]> git.droids-corp.org - protos/libecoli.git/commitdiff
reorganize sources
authorOlivier Matz <zer0@droids-corp.org>
Sun, 12 Aug 2018 13:08:26 +0000 (15:08 +0200)
committerOlivier Matz <zer0@droids-corp.org>
Sun, 12 Aug 2018 13:08:26 +0000 (15:08 +0200)
151 files changed:
Makefile [new file with mode: 0644]
examples/readline/main.c [new file with mode: 0644]
examples/yaml/parse-yaml.c [new file with mode: 0644]
examples/yaml/test.yaml [new file with mode: 0644]
lib/Makefile [deleted file]
lib/ecoli_assert.c [deleted file]
lib/ecoli_assert.h [deleted file]
lib/ecoli_complete.c [deleted file]
lib/ecoli_complete.h [deleted file]
lib/ecoli_config.c [deleted file]
lib/ecoli_config.h [deleted file]
lib/ecoli_init.c [deleted file]
lib/ecoli_init.h [deleted file]
lib/ecoli_keyval.c [deleted file]
lib/ecoli_keyval.h [deleted file]
lib/ecoli_log.c [deleted file]
lib/ecoli_log.h [deleted file]
lib/ecoli_malloc.c [deleted file]
lib/ecoli_malloc.h [deleted file]
lib/ecoli_murmurhash.c [deleted file]
lib/ecoli_murmurhash.h [deleted file]
lib/ecoli_node.c [deleted file]
lib/ecoli_node.h [deleted file]
lib/ecoli_node_any.c [deleted file]
lib/ecoli_node_any.h [deleted file]
lib/ecoli_node_cmd.c [deleted file]
lib/ecoli_node_cmd.h [deleted file]
lib/ecoli_node_dynamic.c [deleted file]
lib/ecoli_node_dynamic.h [deleted file]
lib/ecoli_node_empty.c [deleted file]
lib/ecoli_node_empty.h [deleted file]
lib/ecoli_node_expr.c [deleted file]
lib/ecoli_node_expr.h [deleted file]
lib/ecoli_node_expr_test.c [deleted file]
lib/ecoli_node_file.c [deleted file]
lib/ecoli_node_file.h [deleted file]
lib/ecoli_node_helper.c [deleted file]
lib/ecoli_node_helper.h [deleted file]
lib/ecoli_node_int.c [deleted file]
lib/ecoli_node_int.h [deleted file]
lib/ecoli_node_many.c [deleted file]
lib/ecoli_node_many.h [deleted file]
lib/ecoli_node_none.c [deleted file]
lib/ecoli_node_none.h [deleted file]
lib/ecoli_node_once.c [deleted file]
lib/ecoli_node_once.h [deleted file]
lib/ecoli_node_option.c [deleted file]
lib/ecoli_node_option.h [deleted file]
lib/ecoli_node_or.c [deleted file]
lib/ecoli_node_or.h [deleted file]
lib/ecoli_node_re.c [deleted file]
lib/ecoli_node_re.h [deleted file]
lib/ecoli_node_re_lex.c [deleted file]
lib/ecoli_node_re_lex.h [deleted file]
lib/ecoli_node_seq.c [deleted file]
lib/ecoli_node_seq.h [deleted file]
lib/ecoli_node_sh_lex.c [deleted file]
lib/ecoli_node_sh_lex.h [deleted file]
lib/ecoli_node_space.c [deleted file]
lib/ecoli_node_space.h [deleted file]
lib/ecoli_node_str.c [deleted file]
lib/ecoli_node_str.h [deleted file]
lib/ecoli_node_subset.c [deleted file]
lib/ecoli_node_subset.h [deleted file]
lib/ecoli_parse.c [deleted file]
lib/ecoli_parse.h [deleted file]
lib/ecoli_string.c [deleted file]
lib/ecoli_string.h [deleted file]
lib/ecoli_strvec.c [deleted file]
lib/ecoli_strvec.h [deleted file]
lib/ecoli_test.c [deleted file]
lib/ecoli_test.h [deleted file]
lib/ecoli_vec.c [deleted file]
lib/ecoli_vec.h [deleted file]
lib/main-readline.c [deleted file]
lib/main.c [deleted file]
lib/parse-yaml.c [deleted file]
lib/test.sh [deleted file]
lib/todo.txt [deleted file]
libecoli/ecoli_assert.c [new file with mode: 0644]
libecoli/ecoli_assert.h [new file with mode: 0644]
libecoli/ecoli_complete.c [new file with mode: 0644]
libecoli/ecoli_complete.h [new file with mode: 0644]
libecoli/ecoli_config.c [new file with mode: 0644]
libecoli/ecoli_config.h [new file with mode: 0644]
libecoli/ecoli_init.c [new file with mode: 0644]
libecoli/ecoli_init.h [new file with mode: 0644]
libecoli/ecoli_keyval.c [new file with mode: 0644]
libecoli/ecoli_keyval.h [new file with mode: 0644]
libecoli/ecoli_log.c [new file with mode: 0644]
libecoli/ecoli_log.h [new file with mode: 0644]
libecoli/ecoli_malloc.c [new file with mode: 0644]
libecoli/ecoli_malloc.h [new file with mode: 0644]
libecoli/ecoli_murmurhash.c [new file with mode: 0644]
libecoli/ecoli_murmurhash.h [new file with mode: 0644]
libecoli/ecoli_node.c [new file with mode: 0644]
libecoli/ecoli_node.h [new file with mode: 0644]
libecoli/ecoli_node_any.c [new file with mode: 0644]
libecoli/ecoli_node_any.h [new file with mode: 0644]
libecoli/ecoli_node_cmd.c [new file with mode: 0644]
libecoli/ecoli_node_cmd.h [new file with mode: 0644]
libecoli/ecoli_node_dynamic.c [new file with mode: 0644]
libecoli/ecoli_node_dynamic.h [new file with mode: 0644]
libecoli/ecoli_node_empty.c [new file with mode: 0644]
libecoli/ecoli_node_empty.h [new file with mode: 0644]
libecoli/ecoli_node_expr.c [new file with mode: 0644]
libecoli/ecoli_node_expr.h [new file with mode: 0644]
libecoli/ecoli_node_expr_test.c [new file with mode: 0644]
libecoli/ecoli_node_file.c [new file with mode: 0644]
libecoli/ecoli_node_file.h [new file with mode: 0644]
libecoli/ecoli_node_helper.c [new file with mode: 0644]
libecoli/ecoli_node_helper.h [new file with mode: 0644]
libecoli/ecoli_node_int.c [new file with mode: 0644]
libecoli/ecoli_node_int.h [new file with mode: 0644]
libecoli/ecoli_node_many.c [new file with mode: 0644]
libecoli/ecoli_node_many.h [new file with mode: 0644]
libecoli/ecoli_node_none.c [new file with mode: 0644]
libecoli/ecoli_node_none.h [new file with mode: 0644]
libecoli/ecoli_node_once.c [new file with mode: 0644]
libecoli/ecoli_node_once.h [new file with mode: 0644]
libecoli/ecoli_node_option.c [new file with mode: 0644]
libecoli/ecoli_node_option.h [new file with mode: 0644]
libecoli/ecoli_node_or.c [new file with mode: 0644]
libecoli/ecoli_node_or.h [new file with mode: 0644]
libecoli/ecoli_node_re.c [new file with mode: 0644]
libecoli/ecoli_node_re.h [new file with mode: 0644]
libecoli/ecoli_node_re_lex.c [new file with mode: 0644]
libecoli/ecoli_node_re_lex.h [new file with mode: 0644]
libecoli/ecoli_node_seq.c [new file with mode: 0644]
libecoli/ecoli_node_seq.h [new file with mode: 0644]
libecoli/ecoli_node_sh_lex.c [new file with mode: 0644]
libecoli/ecoli_node_sh_lex.h [new file with mode: 0644]
libecoli/ecoli_node_space.c [new file with mode: 0644]
libecoli/ecoli_node_space.h [new file with mode: 0644]
libecoli/ecoli_node_str.c [new file with mode: 0644]
libecoli/ecoli_node_str.h [new file with mode: 0644]
libecoli/ecoli_node_subset.c [new file with mode: 0644]
libecoli/ecoli_node_subset.h [new file with mode: 0644]
libecoli/ecoli_parse.c [new file with mode: 0644]
libecoli/ecoli_parse.h [new file with mode: 0644]
libecoli/ecoli_string.c [new file with mode: 0644]
libecoli/ecoli_string.h [new file with mode: 0644]
libecoli/ecoli_strvec.c [new file with mode: 0644]
libecoli/ecoli_strvec.h [new file with mode: 0644]
libecoli/ecoli_test.c [new file with mode: 0644]
libecoli/ecoli_test.h [new file with mode: 0644]
libecoli/ecoli_vec.c [new file with mode: 0644]
libecoli/ecoli_vec.h [new file with mode: 0644]
test/test.c [new file with mode: 0644]
test/test.sh [new file with mode: 0755]
todo.txt [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..bb446cf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+
+ECOLI ?= $(abspath ..)
+include $(ECOLI)/mk/ecoli-pre.mk
+
+# output path with trailing slash
+O ?= build/
+
+# XXX -O0
+CFLAGS  = -g -O0 -Wall -Werror -W -Wextra -fPIC -Wmissing-prototypes
+CFLAGS += -I.
+
+# XXX coverage
+CFLAGS += --coverage
+LDFLAGS += --coverage
+#  rm -rf build; rm -rf result; make && ./build/test
+#  lcov -d build -c -t build/test -o test.info && genhtml -o result test.info
+
+
+srcs :=
+srcs += ecoli_assert.c
+srcs += ecoli_complete.c
+srcs += ecoli_config.c
+srcs += ecoli_keyval.c
+srcs += ecoli_init.c
+srcs += ecoli_log.c
+srcs += ecoli_malloc.c
+srcs += ecoli_murmurhash.c
+srcs += ecoli_strvec.c
+srcs += ecoli_test.c
+srcs += ecoli_node.c
+srcs += ecoli_node_any.c
+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_helper.c
+srcs += ecoli_node_int.c
+srcs += ecoli_node_many.c
+srcs += ecoli_node_none.c
+srcs += ecoli_node_once.c
+srcs += ecoli_node_option.c
+srcs += ecoli_node_or.c
+srcs += ecoli_node_re.c
+srcs += ecoli_node_re_lex.c
+srcs += ecoli_node_seq.c
+srcs += ecoli_node_sh_lex.c
+srcs += ecoli_node_space.c
+srcs += ecoli_node_str.c
+srcs += ecoli_node_subset.c
+srcs += ecoli_parse.c
+srcs += ecoli_string.c
+srcs += ecoli_vec.c
+
+shlib-y-$(O)libecoli.so := $(srcs)
+
+ldflags-$(O)test = -rdynamic
+exe-y-$(O)test = $(srcs) main.c
+
+ldflags-$(O)readline = -lreadline -ltermcap
+exe-y-$(O)readline = $(srcs) main-readline.c
+
+ldflags-$(O)parse-yaml = -lyaml
+exe-y-$(O)parse-yaml = $(srcs) parse-yaml.c
+
+include $(ECOLI)/mk/ecoli-post.mk
+
+all: _ecoli_all
+
+clean: _ecoli_clean
+
+.PHONY: clean all
diff --git a/examples/readline/main.c b/examples/readline/main.c
new file mode 100644 (file)
index 0000000..2c81c53
--- /dev/null
@@ -0,0 +1,361 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#define _GNU_SOURCE /* for asprintf */
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include <ecoli_init.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_keyval.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_space.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_sh_lex.h>
+#include <ecoli_node_int.h>
+#include <ecoli_node_option.h>
+#include <ecoli_node_cmd.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_once.h>
+#include <ecoli_node_file.h>
+
+static struct ec_node *commands;
+
+static char *my_completion_entry(const char *s, int state)
+{
+       static struct ec_comp *c;
+       static struct ec_comp_iter *iter;
+       const struct ec_comp_item *item;
+       enum ec_comp_type item_type;
+       const char *item_str, *item_display;
+
+       (void)s;
+
+       /* Don't append a quote. Note: there are still some bugs when
+        * completing a quoted token. */
+       rl_completion_suppress_quote = 1;
+       rl_completer_quote_characters = "\"'";
+
+       if (state == 0) {
+               char *line;
+
+               ec_comp_free(c);
+               line = strdup(rl_line_buffer);
+               if (line == NULL)
+                       return NULL;
+               line[rl_point] = '\0';
+
+               c = ec_node_complete(commands, line);
+               free(line);
+               if (c == NULL)
+                       return NULL;
+
+               ec_comp_iter_free(iter);
+               iter = ec_comp_iter(c, EC_COMP_FULL | EC_COMP_PARTIAL);
+               if (iter == NULL)
+                       return NULL;
+       }
+
+       item = ec_comp_iter_next(iter);
+       if (item == NULL)
+               return NULL;
+
+       item_str = ec_comp_item_get_str(item);
+       if (c->count_full == 1) {
+
+               /* don't add the trailing space for partial completions */
+               if (state == 0) {
+                       item_type = ec_comp_item_get_type(item);
+                       if (item_type == EC_COMP_FULL)
+                               rl_completion_suppress_append = 0;
+                       else
+                               rl_completion_suppress_append = 1;
+               }
+
+               return strdup(item_str);
+       } else if (rl_completion_type == '?') {
+               /* on second try only show the display part */
+               item_display = ec_comp_item_get_display(item);
+               return strdup(item_display);
+       }
+
+       return strdup(item_str);
+}
+
+static char **my_attempted_completion(const char *text, int start, int end)
+{
+       (void)start;
+       (void)end;
+
+       /* remove default file completion */
+       rl_attempted_completion_over = 1;
+
+       return rl_completion_matches(text, my_completion_entry);
+}
+
+/* this function builds the help string */
+static char *get_node_help(const struct ec_comp_item *item)
+{
+       const struct ec_comp_group *grp;
+       const struct ec_parse *state;
+       const struct ec_node *node;
+       char *help = NULL;
+       const char *node_help = NULL;
+       const char *node_desc = NULL;
+
+       grp = ec_comp_item_get_grp(item);
+       state = grp->state;
+       for (state = grp->state; state != NULL;
+            state = ec_parse_get_parent(state)) {
+               node = ec_parse_get_node(state);
+               if (node_help == NULL)
+                       node_help = ec_keyval_get(ec_node_attrs(node), "help");
+               if (node_desc == NULL)
+                       node_desc = ec_node_desc(node);
+       }
+
+       if (node_help == NULL)
+               node_help = "-";
+       if (node_desc == NULL)
+               return NULL;
+
+       if (asprintf(&help, "%-20s %s", node_desc, node_help) < 0)
+               return NULL;
+
+       return help;
+}
+
+static int show_help(int ignore, int invoking_key)
+{
+       struct ec_comp_iter *iter = NULL;
+       const struct ec_comp_group *grp, *prev_grp = NULL;
+       const struct ec_comp_item *item;
+       struct ec_comp *c = NULL;
+       struct ec_parse *p = NULL;
+       char *line = NULL;
+       unsigned int count;
+       char **helps = NULL;
+       int match = 0;
+       int cols;
+
+       (void)ignore;
+       (void)invoking_key;
+
+       line = strdup(rl_line_buffer);
+       if (line == NULL)
+               goto fail;
+
+       /* check if the current line matches */
+       p = ec_node_parse(commands, line);
+       if (ec_parse_matches(p))
+               match = 1;
+       ec_parse_free(p);
+       p = NULL;
+
+       /* complete at current cursor position */
+       line[rl_point] = '\0';
+       c = ec_node_complete(commands, line);
+       free(line);
+       line = NULL;
+       if (c == NULL)
+               goto fail;
+
+       /* let's display one contextual help per node */
+       count = 0;
+       iter = ec_comp_iter(c,
+               EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL);
+       if (iter == NULL)
+               goto fail;
+
+       /* strangely, rl_display_match_list() expects first index at 1 */
+       helps = calloc(match + 1, sizeof(char *));
+       if (helps == NULL)
+               goto fail;
+       if (match)
+               helps[1] = "<return>";
+
+       while ((item = ec_comp_iter_next(iter)) != NULL) {
+               char **tmp;
+
+               /* keep one help per group, skip other items  */
+               grp = ec_comp_item_get_grp(item);
+               if (grp == prev_grp)
+                       continue;
+
+               prev_grp = grp;
+
+               tmp = realloc(helps, (count + match + 2) * sizeof(char *));
+               if (tmp == NULL)
+                       goto fail;
+               helps = tmp;
+               helps[count + match + 1] = get_node_help(item);
+               count++;
+       }
+
+       ec_comp_iter_free(iter);
+       ec_comp_free(c);
+       /* ensure not more than 1 entry per line */
+       rl_get_screen_size(NULL, &cols);
+       rl_display_match_list(helps, count + match, cols);
+       rl_forced_update_display();
+
+       return 0;
+
+fail:
+       ec_comp_iter_free(iter);
+       ec_parse_free(p);
+       free(line);
+       ec_comp_free(c);
+       if (helps != NULL) {
+               while (count--)
+                       free(helps[count + match + 1]);
+       }
+       free(helps);
+
+       return 1;
+}
+
+static int create_commands(void)
+{
+       struct ec_node *cmdlist = NULL, *cmd = NULL;
+
+       cmdlist = ec_node("or", EC_NO_ID);
+       if (cmdlist == NULL)
+               goto fail;
+
+
+       cmd = EC_NODE_SEQ(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "hello"),
+               EC_NODE_OR("name",
+                       ec_node_str("john", "john"),
+                       ec_node_str(EC_NO_ID, "johnny"),
+                       ec_node_str(EC_NO_ID, "mike")
+               ),
+               ec_node_option(EC_NO_ID, ec_node_int("int", 0, 10, 10))
+       );
+       if (cmd == NULL)
+               goto fail;
+       ec_keyval_set(ec_node_attrs(cmd), "help",
+               "say hello to someone several times", NULL);
+       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "john")),
+               "help", "specific help for john", NULL);
+       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")),
+               "help", "the name of the person", NULL);
+       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "int")),
+               "help", "an integer (0-10)", NULL);
+       if (ec_node_or_add(cmdlist, cmd) < 0)
+               goto fail;
+
+
+       cmd = EC_NODE_CMD(EC_NO_ID, "good morning name [count]",
+                       EC_NODE_CMD("name", "bob|bobby|michael"),
+                       ec_node_int("count", 0, 10, 10));
+       if (cmd == NULL)
+               goto fail;
+       ec_keyval_set(ec_node_attrs(cmd), "help",
+               "say good morning to someone several times", NULL);
+       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), "help",
+               "the person to greet", NULL);
+       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "count")), "help",
+               "how many times to greet (0-10)", NULL);
+       if (ec_node_or_add(cmdlist, cmd) < 0)
+               goto fail;
+
+
+       cmd = EC_NODE_CMD(EC_NO_ID,
+                       "buy potatoes,carrots,pumpkins");
+       if (cmd == NULL)
+               goto fail;
+       ec_keyval_set(ec_node_attrs(cmd), "help",
+               "buy some vegetables", NULL);
+       if (ec_node_or_add(cmdlist, cmd) < 0)
+               goto fail;
+
+
+       cmd = EC_NODE_CMD(EC_NO_ID, "eat vegetables",
+                       ec_node_many("vegetables",
+                               EC_NODE_OR(EC_NO_ID,
+                                       ec_node_str(EC_NO_ID, "potatoes"),
+                                       ec_node_once(EC_NO_ID,
+                                               ec_node_str(EC_NO_ID, "carrots")),
+                                       ec_node_once(EC_NO_ID,
+                                               ec_node_str(EC_NO_ID, "pumpkins"))),
+                       1, 0));
+       if (cmd == NULL)
+               goto fail;
+       ec_keyval_set(ec_node_attrs(cmd), "help",
+               "eat vegetables (take some more potatoes)", NULL);
+       if (ec_node_or_add(cmdlist, cmd) < 0)
+               goto fail;
+
+
+       cmd = EC_NODE_SEQ(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "bye")
+       );
+       ec_keyval_set(ec_node_attrs(cmd), "help", "say bye", NULL);
+       if (ec_node_or_add(cmdlist, cmd) < 0)
+               goto fail;
+
+
+       cmd = EC_NODE_SEQ(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "load"),
+               ec_node("file", EC_NO_ID)
+       );
+       ec_keyval_set(ec_node_attrs(cmd), "help", "load a file", NULL);
+       if (ec_node_or_add(cmdlist, cmd) < 0)
+               goto fail;
+
+
+       commands = ec_node_sh_lex(EC_NO_ID, cmdlist);
+       if (commands == NULL)
+               goto fail;
+
+       return 0;
+
+ fail:
+       fprintf(stderr, "cannot initialize nodes\n");
+       ec_node_free(cmdlist);
+       return -1;
+}
+
+int main(void)
+{
+       struct ec_parse *p;
+       char *line;
+
+       if (ec_init() < 0) {
+               fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
+               return 1;
+       }
+
+       if (create_commands() < 0)
+               return 1;
+
+       rl_bind_key('?', show_help);
+       rl_attempted_completion_function = my_attempted_completion;
+
+       while (1) {
+               line = readline("> ");
+               if (line == NULL)
+                       break;
+
+               p = ec_node_parse(commands, line);
+               ec_parse_dump(stdout, p);
+               add_history(line);
+               ec_parse_free(p);
+       }
+
+
+       ec_node_free(commands);
+       return 0;
+
+}
diff --git a/examples/yaml/parse-yaml.c b/examples/yaml/parse-yaml.c
new file mode 100644 (file)
index 0000000..48bcdc7
--- /dev/null
@@ -0,0 +1,552 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <yaml.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+
+/* associate a yaml node to a ecoli node */
+struct pair {
+       const yaml_node_t *ynode;
+       struct ec_node *enode;
+};
+
+/* store the ecoli node tree and the associations yaml_node <-> ec_node */
+struct enode_tree {
+       struct ec_node *root;
+       struct pair *table;
+       size_t table_len;
+};
+
+static struct ec_node *
+parse_ec_node(struct enode_tree *tree,
+       const yaml_document_t *document, const yaml_node_t *ynode);
+
+static struct ec_config *
+parse_ec_config_list(struct enode_tree *tree,
+               const struct ec_config_schema *schema,
+               const yaml_document_t *document, const yaml_node_t *ynode);
+
+static struct ec_config *
+parse_ec_config_dict(struct enode_tree *tree,
+               const struct ec_config_schema *schema,
+               const yaml_document_t *document, const yaml_node_t *ynode);
+
+/* XXX to utils.c ? */
+static int
+parse_llint(const char *str, int64_t *val)
+{
+       char *endptr;
+       int save_errno = errno;
+
+       errno = 0;
+       *val = strtoll(str, &endptr, 0);
+
+       if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
+                       (errno != 0 && *val == 0))
+               return -1;
+
+       if (*endptr != 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       errno = save_errno;
+       return 0;
+}
+
+static int
+parse_ullint(const char *str, uint64_t *val)
+{
+       char *endptr;
+       int save_errno = errno;
+
+       /* since a negative input is silently converted to a positive
+        * one by strtoull(), first check that it is positive */
+       if (strchr(str, '-'))
+               return -1;
+
+       errno = 0;
+       *val = strtoull(str, &endptr, 0);
+
+       if ((errno == ERANGE && *val == ULLONG_MAX) ||
+                       (errno != 0 && *val == 0))
+               return -1;
+
+       if (*endptr != 0)
+               return -1;
+
+       errno = save_errno;
+       return 0;
+}
+
+static int
+parse_bool(const char *str, bool *val)
+{
+       if (!strcasecmp(str, "true")) {
+               *val = true;
+               return 0;
+       } else if (!strcasecmp(str, "false")) {
+               *val = false;
+               return 0;
+       }
+       errno = EINVAL;
+       return -1;
+}
+
+static int
+add_in_table(struct enode_tree *tree, const yaml_node_t *ynode,
+       struct ec_node *enode)
+{
+       struct pair *table = NULL;
+
+       table = realloc(tree->table, (tree->table_len + 1) * sizeof(*table));
+       if (table == NULL)
+               return -1;
+
+       ec_node_clone(enode);
+       table[tree->table_len].ynode = ynode;
+       table[tree->table_len].enode = enode;
+       tree->table = table;
+       tree->table_len++;
+
+       return 0;
+}
+
+static void
+free_tree(struct enode_tree *tree)
+{
+       size_t i;
+
+       if (tree->root != NULL)
+               ec_node_free(tree->root);
+       for (i = 0; i < tree->table_len; i++)
+               ec_node_free(tree->table[i].enode);
+       free(tree->table);
+}
+
+static struct ec_config *
+parse_ec_config(struct enode_tree *tree,
+               const struct ec_config_schema *schema_elt,
+               const yaml_document_t *document, const yaml_node_t *ynode)
+{
+       const struct ec_config_schema *subschema;
+       struct ec_config *config = NULL;
+       struct ec_node *enode = NULL;
+       enum ec_config_type type;
+       const char *value_str;
+       uint64_t u64;
+       int64_t i64;
+       bool boolean;
+
+       type = ec_config_schema_type(schema_elt);
+
+       switch (type) {
+       case EC_CONFIG_TYPE_BOOL:
+               if (ynode->type != YAML_SCALAR_NODE) {
+                       fprintf(stderr, "Boolean should be scalar\n");
+                       goto fail;
+               }
+               value_str = (const char *)ynode->data.scalar.value;
+               if (parse_bool(value_str, &boolean)  < 0) {
+                       fprintf(stderr, "Failed to parse boolean\n");
+                       goto fail;
+               }
+               config = ec_config_bool(boolean);
+               if (config == NULL) {
+                       fprintf(stderr, "Failed to create config\n");
+                       goto fail;
+               }
+               break;
+       case EC_CONFIG_TYPE_INT64:
+               if (ynode->type != YAML_SCALAR_NODE) {
+                       fprintf(stderr, "Int64 should be scalar\n");
+                       goto fail;
+               }
+               value_str = (const char *)ynode->data.scalar.value;
+               if (parse_llint(value_str, &i64)  < 0) {
+                       fprintf(stderr, "Failed to parse i64\n");
+                       goto fail;
+               }
+               config = ec_config_i64(i64);
+               if (config == NULL) {
+                       fprintf(stderr, "Failed to create config\n");
+                       goto fail;
+               }
+               break;
+       case EC_CONFIG_TYPE_UINT64:
+               if (ynode->type != YAML_SCALAR_NODE) {
+                       fprintf(stderr, "Uint64 should be scalar\n");
+                       goto fail;
+               }
+               value_str = (const char *)ynode->data.scalar.value;
+               if (parse_ullint(value_str, &u64)  < 0) {
+                       fprintf(stderr, "Failed to parse u64\n");
+                       goto fail;
+               }
+               config = ec_config_u64(u64);
+               if (config == NULL) {
+                       fprintf(stderr, "Failed to create config\n");
+                       goto fail;
+               }
+               break;
+       case EC_CONFIG_TYPE_STRING:
+               if (ynode->type != YAML_SCALAR_NODE) {
+                       fprintf(stderr, "String should be scalar\n");
+                       goto fail;
+               }
+               value_str = (const char *)ynode->data.scalar.value;
+               config = ec_config_string(value_str);
+               if (config == NULL) {
+                       fprintf(stderr, "Failed to create config\n");
+                       goto fail;
+               }
+               break;
+       case EC_CONFIG_TYPE_NODE:
+               enode = parse_ec_node(tree, document, ynode);
+               if (enode == NULL)
+                       goto fail;
+               config = ec_config_node(enode);
+               if (config == NULL) {
+                       fprintf(stderr, "Failed to create config\n");
+                       goto fail;
+               }
+               break;
+       case EC_CONFIG_TYPE_LIST:
+               subschema = ec_config_schema_sub(schema_elt);
+               if (subschema == NULL) {
+                       fprintf(stderr, "List has no subschema\n");
+                       goto fail;
+               }
+               config = parse_ec_config_list(tree, subschema, document, ynode);
+               if (config == NULL)
+                       goto fail;
+               break;
+       case EC_CONFIG_TYPE_DICT:
+               subschema = ec_config_schema_sub(schema_elt);
+               if (subschema == NULL) {
+                       fprintf(stderr, "Dict has no subschema\n");
+                       goto fail;
+               }
+               config = parse_ec_config_dict(tree, subschema, document, ynode);
+               if (config == NULL)
+                       goto fail;
+               break;
+       default:
+               fprintf(stderr, "Invalid config type %d\n", type);
+               goto fail;
+       }
+
+       return config;
+
+fail:
+       ec_node_free(enode);
+       ec_config_free(config);
+       return NULL;
+}
+
+static struct ec_config *
+parse_ec_config_list(struct enode_tree *tree,
+               const struct ec_config_schema *schema,
+               const yaml_document_t *document, const yaml_node_t *ynode)
+{
+       struct ec_config *config = NULL, *subconfig = NULL;
+       const yaml_node_item_t *item;
+       const yaml_node_t *value;
+
+       (void)tree;
+       (void)schema;
+       (void)document;
+
+       if (ynode->type != YAML_SEQUENCE_NODE) {
+               fprintf(stderr, "Ecoli list config should be a yaml sequence\n");
+               goto fail;
+       }
+
+       config = ec_config_list();
+       if (config == NULL) {
+               fprintf(stderr, "Failed to allocate config\n");
+               goto fail;
+       }
+
+       for (item = ynode->data.sequence.items.start;
+            item < ynode->data.sequence.items.top; item++) {
+               value = document->nodes.start + (*item) - 1; // XXX -1 ?
+               subconfig = parse_ec_config(tree, schema, document, value);
+               if (subconfig == NULL)
+                       goto fail;
+               if (ec_config_list_add(config, subconfig) < 0) {
+                       fprintf(stderr, "Failed to add list entry\n");
+                       goto fail;
+               }
+       }
+
+       return config;
+
+fail:
+       ec_config_free(config);
+       return NULL;
+}
+
+static struct ec_config *
+parse_ec_config_dict(struct enode_tree *tree,
+               const struct ec_config_schema *schema,
+               const yaml_document_t *document, const yaml_node_t *ynode)
+{
+       const struct ec_config_schema *schema_elt;
+       struct ec_config *config = NULL, *subconfig = NULL;
+       const yaml_node_t *key, *value;
+       const yaml_node_pair_t *pair;
+       const char *key_str;
+
+       if (ynode->type != YAML_MAPPING_NODE) {
+               fprintf(stderr, "Ecoli config should be a yaml mapping node\n");
+               goto fail;
+       }
+
+       config = ec_config_dict();
+       if (config == NULL) {
+               fprintf(stderr, "Failed to allocate config\n");
+               goto fail;
+       }
+
+       for (pair = ynode->data.mapping.pairs.start;
+            pair < ynode->data.mapping.pairs.top; pair++) {
+               key = document->nodes.start + pair->key - 1; // XXX -1 ?
+               value = document->nodes.start + pair->value - 1;
+               key_str = (const char *)key->data.scalar.value;
+
+               if (ec_config_key_is_reserved(key_str))
+                       continue;
+               schema_elt = ec_config_schema_lookup(schema, key_str);
+               if (schema_elt == NULL) {
+                       fprintf(stderr, "No such config %s\n", key_str);
+                       goto fail;
+               }
+               subconfig = parse_ec_config(tree, schema_elt, document, value);
+               if (subconfig == NULL)
+                       goto fail;
+               if (ec_config_dict_set(config, key_str, subconfig) < 0) {
+                       fprintf(stderr, "Failed to set dict entry\n");
+                       goto fail;
+               }
+       }
+
+       return config;
+
+fail:
+       ec_config_free(config);
+       return NULL;
+}
+
+static struct ec_node *
+parse_ec_node(struct enode_tree *tree,
+       const yaml_document_t *document, const yaml_node_t *ynode)
+{
+       const struct ec_config_schema *schema;
+       const struct ec_node_type *type = NULL;
+       const char *id = NULL, *help = NULL;
+       struct ec_config *config = NULL;
+       const yaml_node_t *attrs = NULL;
+       const yaml_node_t *key, *value;
+       const yaml_node_pair_t *pair;
+       const char *key_str, *value_str;
+       struct ec_node *enode = NULL;
+
+       if (ynode->type != YAML_MAPPING_NODE) {
+               fprintf(stderr, "Ecoli node should be a yaml mapping node\n");
+               goto fail;
+       }
+
+       for (pair = ynode->data.mapping.pairs.start;
+            pair < ynode->data.mapping.pairs.top; pair++) {
+               key = document->nodes.start + pair->key - 1; // XXX -1 ?
+               value = document->nodes.start + pair->value - 1;
+               key_str = (const char *)key->data.scalar.value;
+               value_str = (const char *)value->data.scalar.value;
+
+               if (!strcmp(key_str, "type")) {
+                       if (type != NULL) {
+                               fprintf(stderr, "Duplicate type\n");
+                               goto fail;
+                       }
+                       if (value->type != YAML_SCALAR_NODE) {
+                               fprintf(stderr, "Type must be a string\n");
+                               goto fail;
+                       }
+                       type = ec_node_type_lookup(value_str);
+                       if (type == NULL) {
+                               fprintf(stderr, "Cannot find type %s\n",
+                                       value_str);
+                               goto fail;
+                       }
+               } else if (!strcmp(key_str, "attrs")) {
+                       if (attrs != NULL) {
+                               fprintf(stderr, "Duplicate attrs\n");
+                               goto fail;
+                       }
+                       if (value->type != YAML_MAPPING_NODE) {
+                               fprintf(stderr, "Attrs must be a maping\n");
+                               goto fail;
+                       }
+                       attrs = value;
+               } else if (!strcmp(key_str, "id")) {
+                       if (id != NULL) {
+                               fprintf(stderr, "Duplicate id\n");
+                               goto fail;
+                       }
+                       if (value->type != YAML_SCALAR_NODE) {
+                               fprintf(stderr, "Id must be a scalar\n");
+                               goto fail;
+                       }
+                       id = value_str;
+               } else if (!strcmp(key_str, "help")) {
+                       if (help != NULL) {
+                               fprintf(stderr, "Duplicate help\n");
+                               goto fail;
+                       }
+                       if (value->type != YAML_SCALAR_NODE) {
+                               fprintf(stderr, "Help must be a scalar\n");
+                               goto fail;
+                       }
+                       help = value_str;
+               }
+       }
+
+       /* create the ecoli node */
+       if (id == NULL)
+               id = EC_NO_ID;
+       enode = ec_node_from_type(type, id);
+       if (enode == NULL) {
+               fprintf(stderr, "Cannot create ecoli node\n");
+               goto fail;
+       }
+       if (add_in_table(tree, ynode, enode) < 0) {
+               fprintf(stderr, "Cannot add node in table\n");
+               goto fail;
+       }
+       if (tree->root == NULL) {
+               ec_node_clone(enode);
+               tree->root = enode;
+       }
+
+       /* create its config */
+       schema = ec_node_type_schema(type);
+       if (schema == NULL) {
+               fprintf(stderr, "No configuration schema for type %s\n",
+                       ec_node_type_name(type));
+               goto fail;
+       }
+
+       config = parse_ec_config_dict(tree, schema, document, ynode);
+       if (config == NULL)
+               goto fail;
+
+       if (ec_node_set_config(enode, config) < 0) {
+               fprintf(stderr, "Failed to set config\n");
+               goto fail;
+       }
+
+       /* add attributes (all as string) */
+       //XXX
+
+       return enode;
+
+fail:
+       ec_node_free(enode);
+       ec_config_free(config);
+       return NULL;
+}
+
+static int
+parse_document(struct enode_tree *tree, const yaml_document_t *document)
+{
+       yaml_node_t *node;
+
+       node = document->nodes.start;
+       if (parse_ec_node(tree, document, node) == NULL)
+               return -1;
+
+       return 0;
+}
+
+static int parse_file(struct enode_tree *tree, const char *filename)
+{
+        FILE *file;
+        yaml_parser_t parser;
+        yaml_document_t document;
+
+        file = fopen(filename, "rb");
+       if (file == NULL) {
+               fprintf(stderr, "Failed to open file %s\n", filename);
+               goto fail_no_doc;
+       }
+
+        if (yaml_parser_initialize(&parser) == 0) {
+               fprintf(stderr, "Failed to initialize yaml parser\n");
+               goto fail_no_doc;
+       }
+
+        yaml_parser_set_input_file(&parser, file);
+
+       if (yaml_parser_load(&parser, &document) == 0) {
+               fprintf(stderr, "Failed to load yaml document\n");
+               goto fail_no_doc;
+       }
+
+       if (yaml_document_get_root_node(&document) == NULL) {
+               fprintf(stderr, "Incomplete document\n"); //XXX check err
+               goto fail;
+       }
+
+       if (parse_document(tree, &document) < 0) {
+               fprintf(stderr, "Failed to parse document\n");
+               goto fail;
+       }
+
+       yaml_document_delete(&document);
+        yaml_parser_delete(&parser);
+        fclose(file);
+
+       return 0;
+
+fail:
+       yaml_document_delete(&document);
+fail_no_doc:
+        yaml_parser_delete(&parser);
+        if (file != NULL)
+               fclose(file);
+
+       return -1;
+}
+
+int
+main(int argc, char *argv[])
+{
+       struct enode_tree tree;
+
+       memset(&tree, 0, sizeof(tree));
+
+       if (argc != 2) {
+               fprintf(stderr, "Invalid args\n");
+               goto fail;
+       }
+       if (parse_file(&tree, argv[1]) < 0) {
+               fprintf(stderr, "Failed to parse file\n");
+               goto fail;
+       }
+       printf("root=%p len=%zd\n", tree.root, tree.table_len);
+       ec_node_dump(stdout, tree.root);
+       free_tree(&tree);
+
+       return 0;
+
+fail:
+       free_tree(&tree);
+       return 1;
+}
diff --git a/examples/yaml/test.yaml b/examples/yaml/test.yaml
new file mode 100644 (file)
index 0000000..072fbce
--- /dev/null
@@ -0,0 +1,16 @@
+type: seq
+attrs:
+  toto: 1
+  titi: 2
+help: Say hello to someone
+children:
+- type: str
+  string: hello
+- type: or
+  id: name
+  help: Name of the person to greet
+  children:
+  - type: str
+    string: john
+  - type: str
+    string: mike
diff --git a/lib/Makefile b/lib/Makefile
deleted file mode 100644 (file)
index bb446cf..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
-
-ECOLI ?= $(abspath ..)
-include $(ECOLI)/mk/ecoli-pre.mk
-
-# output path with trailing slash
-O ?= build/
-
-# XXX -O0
-CFLAGS  = -g -O0 -Wall -Werror -W -Wextra -fPIC -Wmissing-prototypes
-CFLAGS += -I.
-
-# XXX coverage
-CFLAGS += --coverage
-LDFLAGS += --coverage
-#  rm -rf build; rm -rf result; make && ./build/test
-#  lcov -d build -c -t build/test -o test.info && genhtml -o result test.info
-
-
-srcs :=
-srcs += ecoli_assert.c
-srcs += ecoli_complete.c
-srcs += ecoli_config.c
-srcs += ecoli_keyval.c
-srcs += ecoli_init.c
-srcs += ecoli_log.c
-srcs += ecoli_malloc.c
-srcs += ecoli_murmurhash.c
-srcs += ecoli_strvec.c
-srcs += ecoli_test.c
-srcs += ecoli_node.c
-srcs += ecoli_node_any.c
-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_helper.c
-srcs += ecoli_node_int.c
-srcs += ecoli_node_many.c
-srcs += ecoli_node_none.c
-srcs += ecoli_node_once.c
-srcs += ecoli_node_option.c
-srcs += ecoli_node_or.c
-srcs += ecoli_node_re.c
-srcs += ecoli_node_re_lex.c
-srcs += ecoli_node_seq.c
-srcs += ecoli_node_sh_lex.c
-srcs += ecoli_node_space.c
-srcs += ecoli_node_str.c
-srcs += ecoli_node_subset.c
-srcs += ecoli_parse.c
-srcs += ecoli_string.c
-srcs += ecoli_vec.c
-
-shlib-y-$(O)libecoli.so := $(srcs)
-
-ldflags-$(O)test = -rdynamic
-exe-y-$(O)test = $(srcs) main.c
-
-ldflags-$(O)readline = -lreadline -ltermcap
-exe-y-$(O)readline = $(srcs) main-readline.c
-
-ldflags-$(O)parse-yaml = -lyaml
-exe-y-$(O)parse-yaml = $(srcs) parse-yaml.c
-
-include $(ECOLI)/mk/ecoli-post.mk
-
-all: _ecoli_all
-
-clean: _ecoli_clean
-
-.PHONY: clean all
diff --git a/lib/ecoli_assert.c b/lib/ecoli_assert.c
deleted file mode 100644 (file)
index a36d6f6..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-#include <ecoli_assert.h>
-
-void __ec_assert_print(bool expr, const char *expr_str, const char *format, ...)
-{
-       va_list ap;
-
-       if (expr)
-               return;
-
-       /* LCOV_EXCL_START */
-       va_start(ap, format);
-       fprintf(stderr, "assertion failed: '%s' is false\n", expr_str);
-       vfprintf(stderr, format, ap);
-       va_end(ap);
-       abort();
-       /* LCOV_EXCL_END */
-}
diff --git a/lib/ecoli_assert.h b/lib/ecoli_assert.h
deleted file mode 100644 (file)
index fcd2186..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Assert API
- *
- * Helpers to check at runtime if a condition is true, or otherwise
- * either abort (exit program) or return an error.
- */
-
-#ifndef ECOLI_ASSERT_
-#define ECOLI_ASSERT_
-
-#include <stdbool.h>
-
-/**
- * Abort if the condition is false.
- *
- * If expression is false this macro will prints an error message to
- * standard error and terminates the program by calling abort(3).
- *
- * @param expr
- *   The expression to be checked.
- * @param args
- *   The format string, optionally followed by other arguments.
- */
-#define ec_assert_print(expr, args...) \
-       __ec_assert_print(expr, #expr, args)
-
-/* internal */
-void __ec_assert_print(bool expr, const char *expr_str,
-               const char *format, ...);
-
-/**
- * Check a condition or return.
- *
- * If the condition is true, do nothing. If it is false, set
- * errno and return the specified value.
- *
- * @param cond
- *   The condition to test.
- * @param ret
- *   The value to return.
- * @param err
- *   The errno to set.
- */
-#define EC_CHECK_ARG(cond, ret, err) do {                              \
-               if (!(cond)) {                                          \
-                       errno = err;                                    \
-                       return ret;                                     \
-               }                                                       \
-       } while(0)
-
-#endif
diff --git a/lib/ecoli_complete.c b/lib/ecoli_complete.c
deleted file mode 100644 (file)
index 7ad846c..0000000
+++ /dev/null
@@ -1,765 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_node_sh_lex.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_or.h>
-#include <ecoli_complete.h>
-
-EC_LOG_TYPE_REGISTER(comp);
-
-struct ec_comp_item {
-       TAILQ_ENTRY(ec_comp_item) next;
-       enum ec_comp_type type;
-       struct ec_comp_group *grp;
-       char *start;      /* the initial token */
-       char *full;       /* the full token after completion */
-       char *completion; /* chars that are added, NULL if not applicable */
-       char *display;    /* what should be displayed by help/completers */
-       struct ec_keyval *attrs;
-};
-
-struct ec_comp *ec_comp(struct ec_parse *state)
-{
-       struct ec_comp *comp = NULL;
-
-       comp = ec_calloc(1, sizeof(*comp));
-       if (comp == NULL)
-               goto fail;
-
-       comp->attrs = ec_keyval();
-       if (comp->attrs == NULL)
-               goto fail;
-
-       TAILQ_INIT(&comp->groups);
-
-       comp->cur_state = state;
-
-       return comp;
-
- fail:
-       if (comp != NULL)
-               ec_keyval_free(comp->attrs);
-       ec_free(comp);
-
-       return NULL;
-}
-
-struct ec_parse *ec_comp_get_state(struct ec_comp *comp)
-{
-       return comp->cur_state;
-}
-
-int
-ec_node_complete_child(const struct ec_node *node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_parse *child_state, *cur_state;
-       struct ec_comp_group *cur_group;
-       int ret;
-
-       if (ec_node_type(node)->complete == NULL) {
-               errno = ENOTSUP;
-               return -1;
-       }
-
-       /* save previous parse state, prepare child state */
-       cur_state = comp->cur_state;
-       child_state = ec_parse(node);
-       if (child_state == NULL)
-               return -1;
-
-       if (cur_state != NULL)
-               ec_parse_link_child(cur_state, child_state);
-       comp->cur_state = child_state;
-       cur_group = comp->cur_group;
-       comp->cur_group = NULL;
-
-       /* fill the comp struct with items */
-       ret = ec_node_type(node)->complete(node, comp, strvec);
-
-       /* restore parent parse state */
-       if (cur_state != NULL) {
-               ec_parse_unlink_child(cur_state, child_state);
-               assert(!ec_parse_has_child(child_state));
-       }
-       ec_parse_free(child_state);
-       comp->cur_state = cur_state;
-       comp->cur_group = cur_group;
-
-       if (ret < 0)
-               return -1;
-
-       return 0;
-}
-
-struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
-       const struct ec_strvec *strvec)
-{
-       struct ec_comp *comp = NULL;
-       int ret;
-
-       comp = ec_comp(NULL);
-       if (comp == NULL)
-               goto fail;
-
-       ret = ec_node_complete_child(node, comp, strvec);
-       if (ret < 0)
-               goto fail;
-
-       return comp;
-
-fail:
-       ec_comp_free(comp);
-       return NULL;
-}
-
-struct ec_comp *ec_node_complete(const struct ec_node *node,
-       const char *str)
-{
-       struct ec_strvec *strvec = NULL;
-       struct ec_comp *comp;
-
-       errno = ENOMEM;
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       if (ec_strvec_add(strvec, str) < 0)
-               goto fail;
-
-       comp = ec_node_complete_strvec(node, strvec);
-       if (comp == NULL)
-               goto fail;
-
-       ec_strvec_free(strvec);
-       return comp;
-
- fail:
-       ec_strvec_free(strvec);
-       return NULL;
-}
-
-static struct ec_comp_group *
-ec_comp_group(const struct ec_node *node, struct ec_parse *parse)
-{
-       struct ec_comp_group *grp = NULL;
-
-       grp = ec_calloc(1, sizeof(*grp));
-       if (grp == NULL)
-               return NULL;
-
-       grp->attrs = ec_keyval();
-       if (grp->attrs == NULL)
-               goto fail;
-
-       grp->state = ec_parse_dup(parse);
-       if (grp->state == NULL)
-               goto fail;
-
-       grp->node = node;
-       TAILQ_INIT(&grp->items);
-
-       return grp;
-
-fail:
-       if (grp != NULL) {
-               ec_parse_free(grp->state);
-               ec_keyval_free(grp->attrs);
-       }
-       ec_free(grp);
-       return NULL;
-}
-
-static struct ec_comp_item *
-ec_comp_item(enum ec_comp_type type,
-       const char *start, const char *full)
-{
-       struct ec_comp_item *item = NULL;
-       struct ec_keyval *attrs = NULL;
-       char *comp_cp = NULL, *start_cp = NULL;
-       char *full_cp = NULL, *display_cp = NULL;
-
-       if (type == EC_COMP_UNKNOWN && full != NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-       if (type != EC_COMP_UNKNOWN && full == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       item = ec_calloc(1, sizeof(*item));
-       if (item == NULL)
-               goto fail;
-
-       attrs = ec_keyval();
-       if (attrs == NULL)
-               goto fail;
-
-       if (start != NULL) {
-               start_cp = ec_strdup(start);
-               if (start_cp == NULL)
-                       goto fail;
-
-               if (ec_str_startswith(full, start)) {
-                       comp_cp = ec_strdup(&full[strlen(start)]);
-                       if (comp_cp == NULL)
-                               goto fail;
-               }
-       }
-       if (full != NULL) {
-               full_cp = ec_strdup(full);
-               if (full_cp == NULL)
-                       goto fail;
-               display_cp = ec_strdup(full);
-               if (display_cp == NULL)
-                       goto fail;
-       }
-
-       item->type = type;
-       item->start = start_cp;
-       item->full = full_cp;
-       item->completion = comp_cp;
-       item->display = display_cp;
-       item->attrs = attrs;
-
-       return item;
-
-fail:
-       ec_keyval_free(attrs);
-       ec_free(comp_cp);
-       ec_free(start_cp);
-       ec_free(full_cp);
-       ec_free(display_cp);
-       ec_free(item);
-
-       return NULL;
-}
-
-int ec_comp_item_set_display(struct ec_comp_item *item,
-                               const char *display)
-{
-       char *display_copy = NULL;
-
-       if (item == NULL || display == NULL ||
-                       item->type == EC_COMP_UNKNOWN) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       display_copy = ec_strdup(display);
-       if (display_copy == NULL)
-               goto fail;
-
-       ec_free(item->display);
-       item->display = display_copy;
-
-       return 0;
-
-fail:
-       ec_free(display_copy);
-       return -1;
-}
-
-int
-ec_comp_item_set_completion(struct ec_comp_item *item,
-                               const char *completion)
-{
-       char *completion_copy = NULL;
-
-       if (item == NULL || completion == NULL ||
-                       item->type == EC_COMP_UNKNOWN) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       completion_copy = ec_strdup(completion);
-       if (completion_copy == NULL)
-               goto fail;
-
-       ec_free(item->completion);
-       item->completion = completion_copy;
-
-       return 0;
-
-fail:
-       ec_free(completion_copy);
-       return -1;
-}
-
-int
-ec_comp_item_set_str(struct ec_comp_item *item,
-                       const char *str)
-{
-       char *str_copy = NULL;
-
-       if (item == NULL || str == NULL ||
-                       item->type == EC_COMP_UNKNOWN) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       str_copy = ec_strdup(str);
-       if (str_copy == NULL)
-               goto fail;
-
-       ec_free(item->full);
-       item->full = str_copy;
-
-       return 0;
-
-fail:
-       ec_free(str_copy);
-       return -1;
-}
-
-static int
-ec_comp_item_add(struct ec_comp *comp, const struct ec_node *node,
-               struct ec_comp_item *item)
-{
-       if (comp == NULL || item == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       switch (item->type) {
-       case EC_COMP_UNKNOWN:
-               comp->count_unknown++;
-               break;
-       case EC_COMP_FULL:
-               comp->count_full++;
-               break;
-       case EC_COMP_PARTIAL:
-               comp->count_partial++;
-               break;
-       default:
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (comp->cur_group == NULL) {
-               struct ec_comp_group *grp;
-
-               grp = ec_comp_group(node, comp->cur_state);
-               if (grp == NULL)
-                       return -1;
-               TAILQ_INSERT_TAIL(&comp->groups, grp, next);
-               comp->cur_group = grp;
-       }
-
-       comp->count++;
-       TAILQ_INSERT_TAIL(&comp->cur_group->items, item, next);
-       item->grp = comp->cur_group;
-
-       return 0;
-}
-
-const char *
-ec_comp_item_get_str(const struct ec_comp_item *item)
-{
-       return item->full;
-}
-
-const char *
-ec_comp_item_get_display(const struct ec_comp_item *item)
-{
-       return item->display;
-}
-
-const char *
-ec_comp_item_get_completion(const struct ec_comp_item *item)
-{
-       return item->completion;
-}
-
-enum ec_comp_type
-ec_comp_item_get_type(const struct ec_comp_item *item)
-{
-       return item->type;
-}
-
-const struct ec_comp_group *
-ec_comp_item_get_grp(const struct ec_comp_item *item)
-{
-       return item->grp;
-}
-
-const struct ec_node *
-ec_comp_item_get_node(const struct ec_comp_item *item)
-{
-       return ec_comp_item_get_grp(item)->node;
-}
-
-static void
-ec_comp_item_free(struct ec_comp_item *item)
-{
-       if (item == NULL)
-               return;
-
-       ec_free(item->full);
-       ec_free(item->start);
-       ec_free(item->completion);
-       ec_free(item->display);
-       ec_keyval_free(item->attrs);
-       ec_free(item);
-}
-
-int ec_comp_add_item(struct ec_comp *comp,
-                       const struct ec_node *node,
-                       struct ec_comp_item **p_item,
-                       enum ec_comp_type type,
-                       const char *start, const char *full)
-{
-       struct ec_comp_item *item = NULL;
-       int ret;
-
-       item = ec_comp_item(type, start, full);
-       if (item == NULL)
-               return -1;
-
-       ret = ec_comp_item_add(comp, node, item);
-       if (ret < 0)
-               goto fail;
-
-       if (p_item != NULL)
-               *p_item = item;
-
-       return 0;
-
-fail:
-       ec_comp_item_free(item);
-
-       return -1;
-}
-
-/* return a completion item of type "unknown" */
-int
-ec_node_complete_unknown(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       int ret;
-
-       if (ec_strvec_len(strvec) != 1)
-               return 0;
-
-       ret = ec_comp_add_item(comp, gen_node, NULL,
-                               EC_COMP_UNKNOWN, NULL, NULL);
-       if (ret < 0)
-               return ret;
-
-       return 0;
-}
-
-static void ec_comp_group_free(struct ec_comp_group *grp)
-{
-       struct ec_comp_item *item;
-
-       if (grp == NULL)
-               return;
-
-       while (!TAILQ_EMPTY(&grp->items)) {
-               item = TAILQ_FIRST(&grp->items);
-               TAILQ_REMOVE(&grp->items, item, next);
-               ec_comp_item_free(item);
-       }
-       ec_parse_free(ec_parse_get_root(grp->state));
-       ec_keyval_free(grp->attrs);
-       ec_free(grp);
-}
-
-void ec_comp_free(struct ec_comp *comp)
-{
-       struct ec_comp_group *grp;
-
-       if (comp == NULL)
-               return;
-
-       while (!TAILQ_EMPTY(&comp->groups)) {
-               grp = TAILQ_FIRST(&comp->groups);
-               TAILQ_REMOVE(&comp->groups, grp, next);
-               ec_comp_group_free(grp);
-       }
-       ec_keyval_free(comp->attrs);
-       ec_free(comp);
-}
-
-void ec_comp_dump(FILE *out, const struct ec_comp *comp)
-{
-       struct ec_comp_group *grp;
-       struct ec_comp_item *item;
-
-       if (comp == NULL || comp->count == 0) {
-               fprintf(out, "no completion\n");
-               return;
-       }
-
-       fprintf(out, "completion: count=%u full=%u partial=%u unknown=%u\n",
-               comp->count, comp->count_full,
-               comp->count_partial,  comp->count_unknown);
-
-       TAILQ_FOREACH(grp, &comp->groups, next) {
-               fprintf(out, "node=%p, node_type=%s\n",
-                       grp->node, ec_node_type(grp->node)->name);
-               TAILQ_FOREACH(item, &grp->items, next) {
-                       const char *typestr;
-
-                       switch (item->type) {
-                       case EC_COMP_UNKNOWN: typestr = "unknown"; break;
-                       case EC_COMP_FULL: typestr = "full"; break;
-                       case EC_COMP_PARTIAL: typestr = "partial"; break;
-                       default: typestr = "unknown"; break;
-                       }
-
-                       fprintf(out, "  type=%s str=<%s> comp=<%s> disp=<%s>\n",
-                               typestr, item->full, item->completion,
-                               item->display);
-               }
-       }
-}
-
-int ec_comp_merge(struct ec_comp *to,
-               struct ec_comp *from)
-{
-       struct ec_comp_group *grp;
-
-       while (!TAILQ_EMPTY(&from->groups)) {
-               grp = TAILQ_FIRST(&from->groups);
-               TAILQ_REMOVE(&from->groups, grp, next);
-               TAILQ_INSERT_TAIL(&to->groups, grp, next);
-       }
-       to->count += from->count;
-       to->count_full += from->count_full;
-       to->count_partial += from->count_partial;
-       to->count_unknown += from->count_unknown;
-
-       ec_comp_free(from);
-       return 0;
-}
-
-unsigned int ec_comp_count(
-       const struct ec_comp *comp,
-       enum ec_comp_type type)
-{
-       unsigned int count = 0;
-
-       if (comp == NULL)
-               return count;
-
-       if (type & EC_COMP_FULL)
-               count += comp->count_full;
-       if (type & EC_COMP_PARTIAL)
-               count += comp->count_partial;
-       if (type & EC_COMP_UNKNOWN)
-               count += comp->count_unknown;
-
-       return count;
-}
-
-struct ec_comp_iter *
-ec_comp_iter(struct ec_comp *comp,
-       enum ec_comp_type type)
-{
-       struct ec_comp_iter *iter;
-
-       iter = ec_calloc(1, sizeof(*iter));
-       if (iter == NULL)
-               return NULL;
-
-       iter->comp = comp;
-       iter->type = type;
-       iter->cur_node = NULL;
-       iter->cur_match = NULL;
-
-       return iter;
-}
-
-struct ec_comp_item *ec_comp_iter_next(
-       struct ec_comp_iter *iter)
-{
-       struct ec_comp *comp;
-       struct ec_comp_group *cur_node;
-       struct ec_comp_item *cur_match;
-
-       if (iter == NULL)
-               return NULL;
-       comp = iter->comp;
-       if (comp == NULL)
-               return NULL;
-
-       cur_node = iter->cur_node;
-       cur_match = iter->cur_match;
-
-       /* first call */
-       if (cur_node == NULL) {
-               TAILQ_FOREACH(cur_node, &comp->groups, next) {
-                       TAILQ_FOREACH(cur_match, &cur_node->items, next) {
-                               if (cur_match != NULL &&
-                                               cur_match->type & iter->type)
-                                       goto found;
-                       }
-               }
-               return NULL;
-       } else {
-               cur_match = TAILQ_NEXT(cur_match, next);
-               if (cur_match != NULL &&
-                               cur_match->type & iter->type)
-                       goto found;
-               cur_node = TAILQ_NEXT(cur_node, next);
-               while (cur_node != NULL) {
-                       cur_match = TAILQ_FIRST(&cur_node->items);
-                       if (cur_match != NULL &&
-                                       cur_match->type & iter->type)
-                               goto found;
-                       cur_node = TAILQ_NEXT(cur_node, next);
-               }
-               return NULL;
-       }
-
-found:
-       iter->cur_node = cur_node;
-       iter->cur_match = cur_match;
-
-       return iter->cur_match;
-}
-
-void ec_comp_iter_free(struct ec_comp_iter *iter)
-{
-       ec_free(iter);
-}
-
-/* LCOV_EXCL_START */
-static int ec_comp_testcase(void)
-{
-       struct ec_node *node = NULL;
-       struct ec_comp *c = NULL;
-       struct ec_comp_iter *iter = NULL;
-       struct ec_comp_item *item;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-       int testres = 0;
-
-       node = ec_node_sh_lex(EC_NO_ID,
-                       EC_NODE_OR(EC_NO_ID,
-                               ec_node_str("id_x", "xx"),
-                               ec_node_str("id_y", "yy")));
-       if (node == NULL)
-               goto fail;
-
-       c = ec_node_complete(node, "xcdscds");
-       testres |= EC_TEST_CHECK(
-               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 0,
-               "complete count should is not 0\n");
-       ec_comp_free(c);
-
-       c = ec_node_complete(node, "x");
-       testres |= EC_TEST_CHECK(
-               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 1,
-               "complete count should is not 1\n");
-       ec_comp_free(c);
-
-       c = ec_node_complete(node, "");
-       testres |= EC_TEST_CHECK(
-               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 2,
-               "complete count should is not 2\n");
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_comp_dump(f, NULL);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "no completion"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_comp_dump(f, c);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "comp=<xx>"), "bad dump\n");
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "comp=<yy>"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       iter = ec_comp_iter(c, EC_COMP_ALL);
-       item = ec_comp_iter_next(iter);
-       if (item == NULL)
-               goto fail;
-
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_comp_item_get_display(item), "xx"),
-               "bad item display\n");
-       testres |= EC_TEST_CHECK(
-               ec_comp_item_get_type(item) == EC_COMP_FULL,
-               "bad item type\n");
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_x"),
-               "bad item node\n");
-
-       item = ec_comp_iter_next(iter);
-       if (item == NULL)
-               goto fail;
-
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_comp_item_get_display(item), "yy"),
-               "bad item display\n");
-       testres |= EC_TEST_CHECK(
-               ec_comp_item_get_type(item) == EC_COMP_FULL,
-               "bad item type\n");
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_y"),
-               "bad item node\n");
-
-       item = ec_comp_iter_next(iter);
-       testres |= EC_TEST_CHECK(item == NULL, "should be the last item\n");
-
-       ec_comp_iter_free(iter);
-       ec_comp_free(c);
-       ec_node_free(node);
-
-       return testres;
-
-fail:
-       ec_comp_iter_free(iter);
-       ec_comp_free(c);
-       ec_node_free(node);
-       if (f != NULL)
-               fclose(f);
-       free(buf);
-
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_comp_test = {
-       .name = "comp",
-       .test = ec_comp_testcase,
-};
-
-EC_TEST_REGISTER(ec_comp_test);
diff --git a/lib/ecoli_complete.h b/lib/ecoli_complete.h
deleted file mode 100644 (file)
index 1ed67f0..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * API for generating completions item on a node.
- *
- * This file provide helpers to list and manipulate the possible
- * completions for a given input.
- *
- * XXX comp vs item
- */
-
-#ifndef ECOLI_COMPLETE_
-#define ECOLI_COMPLETE_
-
-#include <sys/queue.h>
-#include <sys/types.h>
-#include <stdio.h>
-
-struct ec_node;
-
-enum ec_comp_type { /* XXX should be a define */
-       EC_COMP_UNKNOWN = 0x1,
-       EC_COMP_FULL = 0x2,
-       EC_COMP_PARTIAL = 0x4,
-       EC_COMP_ALL = 0x7,
-};
-
-struct ec_comp_item;
-
-TAILQ_HEAD(ec_comp_item_list, ec_comp_item);
-
-struct ec_comp_group {
-       TAILQ_ENTRY(ec_comp_group) next;
-       const struct ec_node *node;
-       struct ec_comp_item_list items;
-       struct ec_parse *state;
-       struct ec_keyval *attrs;
-};
-
-TAILQ_HEAD(ec_comp_group_list, ec_comp_group);
-
-struct ec_comp {
-       unsigned count;
-       unsigned count_full;
-       unsigned count_partial;
-       unsigned count_unknown;
-       struct ec_parse *cur_state;
-       struct ec_comp_group *cur_group;
-       struct ec_comp_group_list groups;
-       struct ec_keyval *attrs;
-};
-
-/*
- * return a comp object filled with items
- * return NULL on error (nomem?)
- */
-struct ec_comp *ec_node_complete(const struct ec_node *node,
-       const char *str);
-struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
-       const struct ec_strvec *strvec);
-
-/* internal: used by nodes */
-int ec_node_complete_child(const struct ec_node *node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec);
-
-/**
- * Create a completion object (list of completion items).
- *
- *
- */
-struct ec_comp *ec_comp(struct ec_parse *state);
-
-/**
- * Free a completion object and all its items.
- *
- *
- */
-void ec_comp_free(struct ec_comp *comp);
-
-/**
- *
- *
- *
- */
-void ec_comp_dump(FILE *out,
-       const struct ec_comp *comp);
-
-/**
- * Merge items contained in 'from' into 'to'
- *
- * The 'from' comp struct is freed.
- */
-int ec_comp_merge(struct ec_comp *to,
-               struct ec_comp *from);
-
-struct ec_parse *ec_comp_get_state(struct ec_comp *comp);
-
-/* shortcut for ec_comp_item() + ec_comp_item_add() */
-int ec_comp_add_item(struct ec_comp *comp,
-                       const struct ec_node *node,
-                       struct ec_comp_item **p_item,
-                       enum ec_comp_type type,
-                       const char *start, const char *full);
-
-/**
- *
- */
-int ec_comp_item_set_str(struct ec_comp_item *item,
-                       const char *str);
-
-/**
- * Get the string value of a completion item.
- *
- *
- */
-const char *
-ec_comp_item_get_str(const struct ec_comp_item *item);
-
-/**
- * Get the display string value of a completion item.
- *
- *
- */
-const char *
-ec_comp_item_get_display(const struct ec_comp_item *item);
-
-/**
- * Get the completion string value of a completion item.
- *
- *
- */
-const char *
-ec_comp_item_get_completion(const struct ec_comp_item *item);
-
-/**
- * Get the group of a completion item.
- *
- *
- */
-const struct ec_comp_group *
-ec_comp_item_get_grp(const struct ec_comp_item *item);
-
-/**
- * Get the type of a completion item.
- *
- *
- */
-enum ec_comp_type
-ec_comp_item_get_type(const struct ec_comp_item *item);
-
-/**
- * Get the node associated to a completion item.
- *
- *
- */
-const struct ec_node *
-ec_comp_item_get_node(const struct ec_comp_item *item);
-
-/**
- * Set the display value of an item.
- *
- *
- */
-int ec_comp_item_set_display(struct ec_comp_item *item,
-                               const char *display);
-
-/**
- * Set the completion value of an item.
- *
- *
- */
-int ec_comp_item_set_completion(struct ec_comp_item *item,
-                               const char *completion);
-
-/**
- *
- *
- *
- */
-int
-ec_node_complete_unknown(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec);
-
-/**
- *
- *
- *
- */
-unsigned int ec_comp_count(
-       const struct ec_comp *comp,
-       enum ec_comp_type flags);
-
-/**
- *
- *
- *
- */
-struct ec_comp_iter {
-       enum ec_comp_type type;
-       struct ec_comp *comp;
-       struct ec_comp_group *cur_node;
-       struct ec_comp_item *cur_match;
-};
-
-/**
- *
- *
- *
- */
-struct ec_comp_iter *
-ec_comp_iter(struct ec_comp *comp,
-       enum ec_comp_type type);
-
-/**
- *
- *
- *
- */
-struct ec_comp_item *ec_comp_iter_next(
-       struct ec_comp_iter *iter);
-
-/**
- *
- *
- *
- */
-void ec_comp_iter_free(struct ec_comp_iter *iter);
-
-
-#endif
diff --git a/lib/ecoli_config.c b/lib/ecoli_config.c
deleted file mode 100644 (file)
index 0698f07..0000000
+++ /dev/null
@@ -1,1153 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/queue.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <errno.h>
-#include <inttypes.h>
-
-#include <ecoli_string.h>
-#include <ecoli_malloc.h>
-#include <ecoli_keyval.h>
-#include <ecoli_node.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_config.h>
-
-EC_LOG_TYPE_REGISTER(config);
-
-const char *ec_config_reserved_keys[] = {
-       "id",
-       "attrs",
-       "help",
-       "type",
-};
-
-static int
-__ec_config_dump(FILE *out, const char *key, const struct ec_config *config,
-       size_t indent);
-static int
-ec_config_dict_validate(const struct ec_keyval *dict,
-                       const struct ec_config_schema *schema);
-
-bool
-ec_config_key_is_reserved(const char *name)
-{
-       size_t i;
-
-       for (i = 0; i < EC_COUNT_OF(ec_config_reserved_keys); i++) {
-               if (!strcmp(name, ec_config_reserved_keys[i]))
-                       return true;
-       }
-       return false;
-}
-
-/* return ec_value type as a string */
-static const char *
-ec_config_type_str(enum ec_config_type type)
-{
-       switch (type) {
-       case EC_CONFIG_TYPE_BOOL: return "bool";
-       case EC_CONFIG_TYPE_INT64: return "int64";
-       case EC_CONFIG_TYPE_UINT64: return "uint64";
-       case EC_CONFIG_TYPE_STRING: return "string";
-       case EC_CONFIG_TYPE_NODE: return "node";
-       case EC_CONFIG_TYPE_LIST: return "list";
-       case EC_CONFIG_TYPE_DICT: return "dict";
-       default: return "unknown";
-       }
-}
-
-static size_t
-ec_config_schema_len(const struct ec_config_schema *schema)
-{
-       size_t i;
-
-       if (schema == NULL)
-               return 0;
-       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++)
-               ;
-       return i;
-}
-
-static int
-__ec_config_schema_validate(const struct ec_config_schema *schema,
-                       enum ec_config_type type)
-{
-       size_t i, j;
-       int ret;
-
-       if (type == EC_CONFIG_TYPE_LIST) {
-               if (schema[0].key != NULL) {
-                       errno = EINVAL;
-                       EC_LOG(EC_LOG_ERR, "list schema key must be NULL\n");
-                       return -1;
-               }
-       } else if (type == EC_CONFIG_TYPE_DICT) {
-               for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
-                       if (schema[i].key == NULL) {
-                               errno = EINVAL;
-                               EC_LOG(EC_LOG_ERR,
-                                       "dict schema key should not be NULL\n");
-                               return -1;
-                       }
-               }
-       } else {
-               errno = EINVAL;
-               EC_LOG(EC_LOG_ERR, "invalid schema type\n");
-               return -1;
-       }
-
-       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
-               if (schema[i].key != NULL &&
-                               ec_config_key_is_reserved(schema[i].key)) {
-                       errno = EINVAL;
-                       EC_LOG(EC_LOG_ERR,
-                               "key name <%s> is reserved\n", schema[i].key);
-                       return -1;
-               }
-               /* check for duplicate name if more than one element */
-               for (j = i + 1; schema[j].type != EC_CONFIG_TYPE_NONE; j++) {
-                       if (!strcmp(schema[i].key, schema[j].key)) {
-                               errno = EEXIST;
-                               EC_LOG(EC_LOG_ERR,
-                                       "duplicate key <%s> in schema\n",
-                                       schema[i].key);
-                               return -1;
-                       }
-               }
-
-               switch (schema[i].type) {
-               case EC_CONFIG_TYPE_BOOL:
-               case EC_CONFIG_TYPE_INT64:
-               case EC_CONFIG_TYPE_UINT64:
-               case EC_CONFIG_TYPE_STRING:
-               case EC_CONFIG_TYPE_NODE:
-                       if (schema[i].subschema != NULL || ec_config_schema_len(
-                                       schema[i].subschema) != 0) {
-                               errno = EINVAL;
-                               EC_LOG(EC_LOG_ERR,
-                                       "key <%s> should not have subtype/subschema\n",
-                                       schema[i].key);
-                               return -1;
-                       }
-                       break;
-               case EC_CONFIG_TYPE_LIST:
-                       if (schema[i].subschema == NULL || ec_config_schema_len(
-                                       schema[i].subschema) != 1) {
-                               errno = EINVAL;
-                               EC_LOG(EC_LOG_ERR,
-                                       "key <%s> must have subschema of length 1\n",
-                                       schema[i].key);
-                               return -1;
-                       }
-                       break;
-               case EC_CONFIG_TYPE_DICT:
-                       if (schema[i].subschema == NULL || ec_config_schema_len(
-                                       schema[i].subschema) == 0) {
-                               errno = EINVAL;
-                               EC_LOG(EC_LOG_ERR,
-                                       "key <%s> must have subschema\n",
-                                       schema[i].key);
-                               return -1;
-                       }
-                       break;
-               default:
-                       EC_LOG(EC_LOG_ERR, "invalid type for key <%s>\n",
-                               schema[i].key);
-                       errno = EINVAL;
-                       return -1;
-               }
-
-               if (schema[i].subschema == NULL)
-                       continue;
-
-               ret = __ec_config_schema_validate(schema[i].subschema,
-                                               schema[i].type);
-               if (ret < 0) {
-                       EC_LOG(EC_LOG_ERR, "cannot parse subschema %s%s\n",
-                               schema[i].key ? "key=" : "",
-                               schema[i].key ? : "");
-                       return ret;
-               }
-       }
-
-       return 0;
-}
-
-int
-ec_config_schema_validate(const struct ec_config_schema *schema)
-{
-       return __ec_config_schema_validate(schema, EC_CONFIG_TYPE_DICT);
-}
-
-static void
-__ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema,
-                       size_t indent)
-{
-       size_t i;
-
-       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
-               fprintf(out, "%*s" "%s%s%stype=%s desc='%s'\n",
-                       (int)indent * 4, "",
-                       schema[i].key ? "key=": "",
-                       schema[i].key ? : "",
-                       schema[i].key ? " ": "",
-                       ec_config_type_str(schema[i].type),
-                       schema[i].desc);
-               if (schema[i].subschema == NULL)
-                       continue;
-               __ec_config_schema_dump(out, schema[i].subschema,
-                                       indent + 1);
-       }
-}
-
-void
-ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema)
-{
-       fprintf(out, "------------------- schema dump:\n");
-
-       if (schema == NULL) {
-               fprintf(out, "no schema\n");
-               return;
-       }
-
-       __ec_config_schema_dump(out, schema, 0);
-}
-
-enum ec_config_type ec_config_get_type(const struct ec_config *config)
-{
-       return config->type;
-}
-
-struct ec_config *
-ec_config_bool(bool boolean)
-{
-       struct ec_config *value = NULL;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               return NULL;
-
-       value->type = EC_CONFIG_TYPE_BOOL;
-       value->boolean = boolean;
-
-       return value;
-}
-
-struct ec_config *
-ec_config_i64(int64_t i64)
-{
-       struct ec_config *value = NULL;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               return NULL;
-
-       value->type = EC_CONFIG_TYPE_INT64;
-       value->i64 = i64;
-
-       return value;
-}
-
-struct ec_config *
-ec_config_u64(uint64_t u64)
-{
-       struct ec_config *value = NULL;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               return NULL;
-
-       value->type = EC_CONFIG_TYPE_UINT64;
-       value->u64 = u64;
-
-       return value;
-}
-
-/* duplicate string */
-struct ec_config *
-ec_config_string(const char *string)
-{
-       struct ec_config *value = NULL;
-       char *s = NULL;
-
-       if (string == NULL)
-               goto fail;
-
-       s = ec_strdup(string);
-       if (s == NULL)
-               goto fail;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               goto fail;
-
-       value->type = EC_CONFIG_TYPE_STRING;
-       value->string = s;
-
-       return value;
-
-fail:
-       ec_free(value);
-       ec_free(s);
-       return NULL;
-}
-
-/* "consume" the node */
-struct ec_config *
-ec_config_node(struct ec_node *node)
-{
-       struct ec_config *value = NULL;
-
-       if (node == NULL)
-               goto fail;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               goto fail;
-
-       value->type = EC_CONFIG_TYPE_NODE;
-       value->node = node;
-
-       return value;
-
-fail:
-       ec_node_free(node);
-       ec_free(value);
-       return NULL;
-}
-
-struct ec_config *
-ec_config_dict(void)
-{
-       struct ec_config *value = NULL;
-       struct ec_keyval *dict = NULL;
-
-       dict = ec_keyval();
-       if (dict == NULL)
-               goto fail;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               goto fail;
-
-       value->type = EC_CONFIG_TYPE_DICT;
-       value->dict = dict;
-
-       return value;
-
-fail:
-       ec_keyval_free(dict);
-       ec_free(value);
-       return NULL;
-}
-
-struct ec_config *
-ec_config_list(void)
-{
-       struct ec_config *value = NULL;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               return NULL;
-
-       value->type = EC_CONFIG_TYPE_LIST;
-       TAILQ_INIT(&value->list);
-
-       return value;
-}
-
-const struct ec_config_schema *
-ec_config_schema_lookup(const struct ec_config_schema *schema,
-                       const char *key)
-{
-       size_t i;
-
-       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
-               if (!strcmp(key, schema[i].key))
-                       return &schema[i];
-       }
-
-       errno = ENOENT;
-       return NULL;
-}
-
-enum ec_config_type
-ec_config_schema_type(const struct ec_config_schema *schema_elt)
-{
-       return schema_elt->type;
-}
-
-const struct ec_config_schema *
-ec_config_schema_sub(const struct ec_config_schema *schema_elt)
-{
-       return schema_elt->subschema;
-}
-
-void
-ec_config_free(struct ec_config *value)
-{
-       if (value == NULL)
-               return;
-
-       switch (value->type) {
-       case EC_CONFIG_TYPE_STRING:
-               ec_free(value->string);
-               break;
-       case EC_CONFIG_TYPE_NODE:
-               ec_node_free(value->node);
-               break;
-       case EC_CONFIG_TYPE_LIST:
-               while (!TAILQ_EMPTY(&value->list)) {
-                       struct ec_config *v;
-                       v = TAILQ_FIRST(&value->list);
-                       TAILQ_REMOVE(&value->list, v, next);
-                       ec_config_free(v);
-               }
-               break;
-       case EC_CONFIG_TYPE_DICT:
-               ec_keyval_free(value->dict);
-               break;
-       default:
-               break;
-       }
-
-       ec_free(value);
-}
-
-static int
-ec_config_list_cmp(const struct ec_config_list *list1,
-               const struct ec_config_list *list2)
-{
-       const struct ec_config *v1, *v2;
-
-       for (v1 = TAILQ_FIRST(list1), v2 = TAILQ_FIRST(list2);
-            v1 != NULL && v2 != NULL;
-            v1 = TAILQ_NEXT(v1, next), v2 = TAILQ_NEXT(v2, next)) {
-               if (ec_config_cmp(v1, v2))
-                       return -1;
-       }
-       if (v1 != NULL || v2 != NULL)
-               return -1;
-
-       return 0;
-}
-
-/* XXX -> ec_keyval_cmp() */
-static int
-ec_config_dict_cmp(const struct ec_keyval *d1,
-               const struct ec_keyval *d2)
-{
-       const struct ec_config *v1, *v2;
-       struct ec_keyval_iter *iter = NULL;
-       const char *key;
-
-       if (ec_keyval_len(d1) != ec_keyval_len(d2))
-               return -1;
-
-       for (iter = ec_keyval_iter(d1);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               key = ec_keyval_iter_get_key(iter);
-               v1 = ec_keyval_iter_get_val(iter);
-               v2 = ec_keyval_get(d2, key);
-
-               if (ec_config_cmp(v1, v2))
-                       goto fail;
-       }
-
-       ec_keyval_iter_free(iter);
-       return 0;
-
-fail:
-       ec_keyval_iter_free(iter);
-       return -1;
-}
-
-int
-ec_config_cmp(const struct ec_config *value1,
-               const struct ec_config *value2)
-{
-       if (value1 == NULL || value2 == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (value1->type != value2->type)
-               return -1;
-
-       switch (value1->type) {
-       case EC_CONFIG_TYPE_BOOL:
-               if (value1->boolean == value2->boolean)
-                       return 0;
-       case EC_CONFIG_TYPE_INT64:
-               if (value1->i64 == value2->i64)
-                       return 0;
-       case EC_CONFIG_TYPE_UINT64:
-               if (value1->u64 == value2->u64)
-                       return 0;
-       case EC_CONFIG_TYPE_STRING:
-               if (!strcmp(value1->string, value2->string))
-                       return 0;
-       case EC_CONFIG_TYPE_NODE:
-               if (value1->node == value2->node)
-                       return 0;
-       case EC_CONFIG_TYPE_LIST:
-               return ec_config_list_cmp(&value1->list, &value2->list);
-       case EC_CONFIG_TYPE_DICT:
-               return ec_config_dict_cmp(value1->dict, value2->dict);
-       default:
-               break;
-       }
-
-       return -1;
-}
-
-static int
-ec_config_list_validate(const struct ec_config_list *list,
-                       const struct ec_config_schema *sch)
-{
-       const struct ec_config *value;
-
-       TAILQ_FOREACH(value, list, next) {
-               if (value->type != sch->type) {
-                       errno = EBADMSG;
-                       return -1;
-               }
-
-               if (value->type == EC_CONFIG_TYPE_LIST) {
-                       if (ec_config_list_validate(&value->list,
-                                                       sch->subschema) < 0)
-                               return -1;
-               } else if (value->type == EC_CONFIG_TYPE_DICT) {
-                       if (ec_config_dict_validate(value->dict,
-                                       sch->subschema) < 0)
-                               return -1;
-               }
-       }
-
-       return 0;
-}
-
-static int
-ec_config_dict_validate(const struct ec_keyval *dict,
-                       const struct ec_config_schema *schema)
-{
-       const struct ec_config *value;
-       struct ec_keyval_iter *iter = NULL;
-       const struct ec_config_schema *sch;
-       const char *key;
-
-       for (iter = ec_keyval_iter(dict);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-
-               key = ec_keyval_iter_get_key(iter);
-               value = ec_keyval_iter_get_val(iter);
-               sch = ec_config_schema_lookup(schema, key);
-               if (sch == NULL || sch->type != value->type) {
-                       errno = EBADMSG;
-                       goto fail;
-               }
-               if (value->type == EC_CONFIG_TYPE_LIST) {
-                       if (ec_config_list_validate(&value->list,
-                                                       sch->subschema) < 0)
-                               goto fail;
-               } else if (value->type == EC_CONFIG_TYPE_DICT) {
-                       if (ec_config_dict_validate(value->dict,
-                                       sch->subschema) < 0)
-                               goto fail;
-               }
-       }
-
-       ec_keyval_iter_free(iter);
-       return 0;
-
-fail:
-       ec_keyval_iter_free(iter);
-       return -1;
-}
-
-int
-ec_config_validate(const struct ec_config *dict,
-               const struct ec_config_schema *schema)
-{
-       if (dict->type != EC_CONFIG_TYPE_DICT || schema == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (ec_config_dict_validate(dict->dict, schema) < 0)
-               goto fail;
-
-       return 0
-;
-fail:
-       return -1;
-}
-
-struct ec_config *
-ec_config_dict_get(const struct ec_config *config, const char *key)
-{
-       if (config == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       if (config->type != EC_CONFIG_TYPE_DICT) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       return ec_keyval_get(config->dict, key);
-}
-
-struct ec_config *
-ec_config_list_first(struct ec_config *list)
-{
-       if (list  == NULL || list->type != EC_CONFIG_TYPE_LIST) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       return TAILQ_FIRST(&list->list);
-}
-
-struct ec_config *
-ec_config_list_next(struct ec_config *list, struct ec_config *config)
-{
-       (void)list;
-       return TAILQ_NEXT(config, next);
-}
-
-/* value is consumed */
-int ec_config_dict_set(struct ec_config *config, const char *key,
-               struct ec_config *value)
-{
-       void (*free_cb)(struct ec_config *) = ec_config_free;
-
-       if (config == NULL || key == NULL || value == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-       if (config->type != EC_CONFIG_TYPE_DICT) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       return ec_keyval_set(config->dict, key, value,
-                       (void (*)(void *))free_cb);
-
-fail:
-       ec_config_free(value);
-       return -1;
-}
-
-int ec_config_dict_del(struct ec_config *config, const char *key)
-{
-       if (config == NULL || key == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-       if (config->type != EC_CONFIG_TYPE_DICT) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       return ec_keyval_del(config->dict, key);
-}
-
-/* value is consumed */
-int
-ec_config_list_add(struct ec_config *list,
-               struct ec_config *value)
-{
-       if (list == NULL || list->type != EC_CONFIG_TYPE_LIST || value == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       TAILQ_INSERT_TAIL(&list->list, value, next);
-
-       return 0;
-
-fail:
-       ec_config_free(value);
-       return -1;
-}
-
-int ec_config_list_del(struct ec_config *list, struct ec_config *config)
-{
-       if (list == NULL || list->type != EC_CONFIG_TYPE_LIST) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       TAILQ_REMOVE(&list->list, config, next);
-       ec_config_free(config);
-       return 0;
-}
-
-static struct ec_config *
-ec_config_list_dup(const struct ec_config_list *list)
-{
-       struct ec_config *dup = NULL, *v, *value;
-
-       dup = ec_config_list();
-       if (dup == NULL)
-               goto fail;
-
-       TAILQ_FOREACH(v, list, next) {
-               value = ec_config_dup(v);
-               if (value == NULL)
-                       goto fail;
-               if (ec_config_list_add(dup, value) < 0)
-                       goto fail;
-       }
-
-       return dup;
-
-fail:
-       ec_config_free(dup);
-       return NULL;
-}
-
-static struct ec_config *
-ec_config_dict_dup(const struct ec_keyval *dict)
-{
-       struct ec_config *dup = NULL, *value;
-       struct ec_keyval_iter *iter = NULL;
-       const char *key;
-
-       dup = ec_config_dict();
-       if (dup == NULL)
-               goto fail;
-
-       for (iter = ec_keyval_iter(dict);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               key = ec_keyval_iter_get_key(iter);
-               value = ec_config_dup(ec_keyval_iter_get_val(iter));
-               if (value == NULL)
-                       goto fail;
-               if (ec_config_dict_set(dup, key, value) < 0)
-                       goto fail;
-       }
-       ec_keyval_iter_free(iter);
-
-       return dup;
-
-fail:
-       ec_config_free(dup);
-       ec_keyval_iter_free(iter);
-       return NULL;
-}
-
-struct ec_config *
-ec_config_dup(const struct ec_config *config)
-{
-       if (config == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       switch (config->type) {
-       case EC_CONFIG_TYPE_BOOL:
-               return ec_config_bool(config->boolean);
-       case EC_CONFIG_TYPE_INT64:
-               return ec_config_i64(config->i64);
-       case EC_CONFIG_TYPE_UINT64:
-               return ec_config_u64(config->u64);
-       case EC_CONFIG_TYPE_STRING:
-               return ec_config_string(config->string);
-       case EC_CONFIG_TYPE_NODE:
-               return ec_config_node(ec_node_clone(config->node));
-       case EC_CONFIG_TYPE_LIST:
-               return ec_config_list_dup(&config->list);
-       case EC_CONFIG_TYPE_DICT:
-               return ec_config_dict_dup(config->dict);
-       default:
-               errno = EINVAL;
-               break;
-       }
-
-       return NULL;
-}
-
-static int
-ec_config_list_dump(FILE *out, const char *key,
-               const struct ec_config_list *list, size_t indent)
-{
-       const struct ec_config *v;
-
-       fprintf(out, "%*s" "%s%s%stype=list\n", (int)indent * 4, "",
-               key ? "key=": "",
-               key ? key: "",
-               key ? " ": "");
-
-       TAILQ_FOREACH(v, list, next) {
-               if (__ec_config_dump(out, NULL, v, indent + 1) < 0)
-                       return -1;
-       }
-
-       return 0;
-}
-
-static int
-ec_config_dict_dump(FILE *out, const char *key, const struct ec_keyval *dict,
-               size_t indent)
-{
-       const struct ec_config *value;
-       struct ec_keyval_iter *iter;
-       const char *k;
-
-       fprintf(out, "%*s" "%s%s%stype=dict\n", (int)indent * 4, "",
-               key ? "key=": "",
-               key ? key: "",
-               key ? " ": "");
-
-       for (iter = ec_keyval_iter(dict);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               k = ec_keyval_iter_get_key(iter);
-               value = ec_keyval_iter_get_val(iter);
-               if (__ec_config_dump(out, k, value, indent + 1) < 0)
-                       goto fail;
-       }
-       ec_keyval_iter_free(iter);
-       return 0;
-
-fail:
-       ec_keyval_iter_free(iter);
-       return -1;
-}
-
-static int
-__ec_config_dump(FILE *out, const char *key, const struct ec_config *value,
-               size_t indent)
-{
-       char *val_str = NULL;
-
-       switch (value->type) {
-       case EC_CONFIG_TYPE_BOOL:
-               if (value->boolean)
-                       ec_asprintf(&val_str, "true");
-               else
-                       ec_asprintf(&val_str, "false");
-               break;
-       case EC_CONFIG_TYPE_INT64:
-               ec_asprintf(&val_str, "%"PRIu64, value->u64);
-               break;
-       case EC_CONFIG_TYPE_UINT64:
-               ec_asprintf(&val_str, "%"PRIi64, value->i64);
-               break;
-       case EC_CONFIG_TYPE_STRING:
-               ec_asprintf(&val_str, "%s", value->string);
-               break;
-       case EC_CONFIG_TYPE_NODE:
-               ec_asprintf(&val_str, "%p", value->node);
-               break;
-       case EC_CONFIG_TYPE_LIST:
-               return ec_config_list_dump(out, key, &value->list, indent);
-       case EC_CONFIG_TYPE_DICT:
-               return ec_config_dict_dump(out, key, value->dict, indent);
-       default:
-               errno = EINVAL;
-               break;
-       }
-
-       /* errno is already set on error */
-       if (val_str == NULL)
-               goto fail;
-
-       fprintf(out, "%*s" "%s%s%stype=%s val=%s\n", (int)indent * 4, "",
-               key ? "key=": "",
-               key ? key: "",
-               key ? " ": "",
-               ec_config_type_str(value->type), val_str);
-
-       ec_free(val_str);
-       return 0;
-
-fail:
-       ec_free(val_str);
-       return -1;
-}
-
-void
-ec_config_dump(FILE *out, const struct ec_config *config)
-{
-       fprintf(out, "------------------- config dump:\n");
-
-       if (config == NULL) {
-               fprintf(out, "no config\n");
-               return;
-       }
-
-       if (__ec_config_dump(out, NULL, config, 0) < 0)
-               fprintf(out, "error while dumping\n");
-}
-
-/* LCOV_EXCL_START */
-static const struct ec_config_schema sch_intlist_elt[] = {
-       {
-               .desc = "This is a description for int",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema sch_dict[] = {
-       {
-               .key = "my_int",
-               .desc = "This is a description for int",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .key = "my_int2",
-               .desc = "This is a description for int2",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema sch_dictlist_elt[] = {
-       {
-               .desc = "This is a description for dict",
-               .type = EC_CONFIG_TYPE_DICT,
-               .subschema = sch_dict,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema sch_baseconfig[] = {
-       {
-               .key = "my_bool",
-               .desc = "This is a description for bool",
-               .type = EC_CONFIG_TYPE_BOOL,
-       },
-       {
-               .key = "my_int",
-               .desc = "This is a description for int",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .key = "my_string",
-               .desc = "This is a description for string",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .key = "my_node",
-               .desc = "This is a description for node",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .key = "my_intlist",
-               .desc = "This is a description for list",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = sch_intlist_elt,
-       },
-       {
-               .key = "my_dictlist",
-               .desc = "This is a description for list",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = sch_dictlist_elt,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_config_testcase(void)
-{
-       struct ec_node *node = NULL;
-       struct ec_keyval *dict = NULL;
-       const struct ec_config *value = NULL;
-       struct ec_config *config = NULL, *config2 = NULL;
-       struct ec_config *list = NULL, *subconfig = NULL;
-       struct ec_config *list_, *config_;
-       int testres = 0;
-       int ret;
-
-       testres |= EC_TEST_CHECK(ec_config_key_is_reserved("id"),
-               "'id' should be reserved");
-       testres |= EC_TEST_CHECK(!ec_config_key_is_reserved("foo"),
-               "'foo' should not be reserved");
-
-       node = ec_node("empty", EC_NO_ID);
-       if (node == NULL)
-               goto fail;
-
-       if (ec_config_schema_validate(sch_baseconfig) < 0) {
-               EC_LOG(EC_LOG_ERR, "invalid config schema\n");
-               goto fail;
-       }
-
-       ec_config_schema_dump(stdout, sch_baseconfig);
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "my_bool", ec_config_bool(true));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set boolean");
-       value = ec_config_dict_get(config, "my_bool");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_BOOL &&
-               value->boolean == true,
-               "unexpected boolean value");
-
-       ret = ec_config_dict_set(config, "my_int", ec_config_i64(1234));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(config, "my_int");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 1234,
-               "unexpected int value");
-
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(config, sch_baseconfig) == 0,
-               "cannot validate config\n");
-
-       ret = ec_config_dict_set(config, "my_string", ec_config_string("toto"));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set string");
-       value = ec_config_dict_get(config, "my_string");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_STRING &&
-               !strcmp(value->string, "toto"),
-               "unexpected string value");
-
-       list = ec_config_list();
-       if (list == NULL)
-               goto fail;
-
-       subconfig = ec_config_dict();
-       if (subconfig == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(1));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(subconfig, "my_int");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 1,
-               "unexpected int value");
-
-       ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(2));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(subconfig, "my_int2");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 2,
-               "unexpected int value");
-
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(subconfig, sch_dict) == 0,
-               "cannot validate subconfig\n");
-
-       ret = ec_config_list_add(list, subconfig);
-       subconfig = NULL; /* freed */
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add in list");
-
-       subconfig = ec_config_dict();
-       if (subconfig == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(3));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(subconfig, "my_int");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 3,
-               "unexpected int value");
-
-       ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(4));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(subconfig, "my_int2");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 4,
-               "unexpected int value");
-
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(subconfig, sch_dict) == 0,
-               "cannot validate subconfig\n");
-
-       ret = ec_config_list_add(list, subconfig);
-       subconfig = NULL; /* freed */
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add in list");
-
-       ret = ec_config_dict_set(config, "my_dictlist", list);
-       list = NULL;
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set list");
-
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(config, sch_baseconfig) == 0,
-               "cannot validate config\n");
-
-       list_ = ec_config_dict_get(config, "my_dictlist");
-       for (config_ = ec_config_list_first(list_); config_ != NULL;
-            config_ = ec_config_list_next(list_, config_)) {
-               ec_config_dump(stdout, config_);
-       }
-
-       ec_config_dump(stdout, config);
-
-       config2 = ec_config_dup(config);
-       testres |= EC_TEST_CHECK(config2 != NULL, "cannot duplicate config");
-       testres |= EC_TEST_CHECK(
-               ec_config_cmp(config, config2) == 0,
-               "fail to compare config");
-       ec_config_free(config2);
-       config2 = NULL;
-
-       /* remove the first element */
-       ec_config_list_del(list_, ec_config_list_first(list_));
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(config, sch_baseconfig) == 0,
-               "cannot validate config\n");
-
-       ec_config_dump(stdout, config);
-
-       ec_config_free(list);
-       ec_config_free(subconfig);
-       ec_config_free(config);
-       ec_keyval_free(dict);
-       ec_node_free(node);
-
-       return testres;
-
-fail:
-       ec_config_free(list);
-       ec_config_free(subconfig);
-       ec_config_free(config);
-       ec_config_free(config2);
-       ec_keyval_free(dict);
-       ec_node_free(node);
-
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_config_test = {
-       .name = "config",
-       .test = ec_config_testcase,
-};
-
-EC_TEST_REGISTER(ec_config_test);
diff --git a/lib/ecoli_config.h b/lib/ecoli_config.h
deleted file mode 100644 (file)
index 9d7c628..0000000
+++ /dev/null
@@ -1,392 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_CONFIG_
-#define ECOLI_CONFIG_
-
-#include <sys/queue.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-
-#ifndef EC_COUNT_OF //XXX
-#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
-               ((size_t)(!(sizeof(x) % sizeof(0[x])))))
-#endif
-
-struct ec_config;
-struct ec_keyval;
-
-/**
- * The type identifier for a config value.
- */
-enum ec_config_type {
-       EC_CONFIG_TYPE_NONE = 0,
-       EC_CONFIG_TYPE_BOOL,
-       EC_CONFIG_TYPE_INT64,
-       EC_CONFIG_TYPE_UINT64,
-       EC_CONFIG_TYPE_STRING,
-       EC_CONFIG_TYPE_NODE,
-       EC_CONFIG_TYPE_LIST,
-       EC_CONFIG_TYPE_DICT,
-};
-
-/**
- * Structure describing the format of a configuration value.
- *
- * This structure is used in a const array which is referenced by a
- * struct ec_config. Each entry of the array represents a key/value
- * storage of the configuration dictionary.
- */
-struct ec_config_schema {
-       const char *key;          /**< The key string (NULL for list elts). */
-       const char *desc;         /**< A description of the value. */
-       enum ec_config_type type; /**< Type of the value */
-
-       /** If type is dict or list, the schema of the dict or list
-        * elements. Else must be NULL. */
-       const struct ec_config_schema *subschema;
-};
-
-TAILQ_HEAD(ec_config_list, ec_config);
-
-/**
- * Structure storing the configuration data.
- */
-struct ec_config {
-       /** type of value stored in the union */
-       enum ec_config_type type;
-
-       union {
-               bool boolean;   /** Boolean value */
-               int64_t i64;    /** Signed integer value */
-               uint64_t u64;   /** Unsigned integer value */
-               char *string;   /** String value */
-               struct ec_node *node;       /** Node value */
-               struct ec_keyval *dict;     /** Hash table value */
-               struct ec_config_list list; /** List value */
-       };
-
-       /**
-        * Next in list, only valid if type is list.
-        */
-       TAILQ_ENTRY(ec_config) next;
-};
-
-/* schema */
-
-/**
- * Validate a configuration schema array.
- *
- * @param schema
- *   Pointer to the first element of the schema array. The array
- *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
- * @return
- *   0 if the schema is valid, or -1 on error (errno is set).
- */
-int ec_config_schema_validate(const struct ec_config_schema *schema);
-
-/**
- * Dump a configuration schema array.
- *
- * @param out
- *   Output stream on which the dump will be sent.
- * @param schema
- *   Pointer to the first element of the schema array. The array
- *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
- */
-void ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema);
-
-/**
- * Find a schema entry matching the key.
- *
- * Browse the schema array and lookup for the given key.
- *
- * @param schema
- *   Pointer to the first element of the schema array. The array
- *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
- * @return
- *   The schema entry if it matches a key, or NULL if not found.
- */
-const struct ec_config_schema *
-ec_config_schema_lookup(const struct ec_config_schema *schema,
-                       const char *key);
-
-/**
- * Get the type of a schema entry.
- *
- * @param schema_elt
- *   Pointer to an element of the schema array.
- * @return
- *   The type of the schema entry.
- */
-enum ec_config_type
-ec_config_schema_type(const struct ec_config_schema *schema_elt);
-
-/**
- * Get the subschema of a schema entry.
- *
- * @param schema_elt
- *   Pointer to an element of the schema array.
- * @return
- *   The subschema if any, or NULL.
- */
-const struct ec_config_schema *
-ec_config_schema_sub(const struct ec_config_schema *schema_elt);
-
-/**
- * Check if a key name is reserved in a config dict.
- *
- * Some key names are reserved and should not be used in configs.
- *
- * @param name
- *   The name of the key to test.
- * @return
- *   True if the key name is reserved and must not be used, else false.
- */
-bool ec_config_key_is_reserved(const char *name);
-
-/**
- * Array of reserved key names.
- */
-extern const char *ec_config_reserved_keys[];
-
-
-/* config */
-
-/**
- * Get the type of the configuration.
- *
- * @param config
- *   The configuration.
- * @return
- *   The configuration type.
- */
-enum ec_config_type ec_config_get_type(const struct ec_config *config);
-
-/**
- * Create a boolean configuration value.
- *
- * @param boolean
- *   The boolean value to be set.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_bool(bool boolean);
-
-/**
- * Create a signed integer configuration value.
- *
- * @param i64
- *   The signed integer value to be set.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_i64(int64_t i64);
-
-/**
- * Create an unsigned configuration value.
- *
- * @param u64
- *   The unsigned integer value to be set.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_u64(uint64_t u64);
-
-/**
- * Create a string configuration value.
- *
- * @param string
- *   The string value to be set. The string is copied into the
- *   configuration object.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_string(const char *string);
-
-/**
- * Create a node configuration value.
- *
- * @param node
- *   The node pointer to be set. The node is "consumed" by
- *   the function and should not be used by the caller, even
- *   on error. The caller can use ec_node_clone() to keep a
- *   reference on the node.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_node(struct ec_node *node);
-
-/**
- * Create a hash table configuration value.
- *
- * @return
- *   A configuration object containing an empty hash table, or NULL on
- *   error (errno is set).
- */
-struct ec_config *ec_config_dict(void);
-
-/**
- * Create a list configuration value.
- *
- * @return
- *   The configuration object containing an empty list, or NULL on
- *   error (errno is set).
- */
-struct ec_config *ec_config_list(void);
-
-/**
- * Add a config object into a list.
- *
- * @param list
- *   The list configuration in which the value will be added.
- * @param value
- *   The value configuration to add in the list. The value object
- *   will be freed when freeing the list object. On error, the
- *   value object is also freed.
- * @return
- *   0 on success, else -1 (errno is set).
- */
-int ec_config_list_add(struct ec_config *list, struct ec_config *value);
-
-/**
- * Remove an element from a list.
- *
- * The element is freed and should not be accessed.
- *
- * @param list
- *   The list configuration.
- * @param config
- *   The element to remove from the list.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_config_list_del(struct ec_config *list, struct ec_config *config);
-
-/**
- * Validate a configuration.
- *
- * @param dict
- *   A hash table configuration to validate.
- * @param schema
- *   Pointer to the first element of the schema array. The array
- *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_config_validate(const struct ec_config *dict,
-               const struct ec_config_schema *schema);
-
-/**
- * Set a value in a hash table configuration
- *
- * @param dict
- *   A hash table configuration to validate.
- * @param key
- *   The key to update.
- * @param value
- *   The value to set. The value object will be freed when freeing the
- *   dict object. On error, the value object is also freed.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_config_dict_set(struct ec_config *dict, const char *key,
-               struct ec_config *value);
-
-/**
- * Remove an element from a hash table configuration.
- *
- * The element is freed and should not be accessed.
- *
- * @param dict
- *   A hash table configuration to validate.
- * @param key
- *   The key of the configuration to delete.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_config_dict_del(struct ec_config *config, const char *key);
-
-/**
- * Compare two configurations.
- */
-int ec_config_cmp(const struct ec_config *config1,
-               const struct ec_config *config2);
-
-/**
- * Get configuration value.
- */
-struct ec_config *ec_config_dict_get(const struct ec_config *config,
-                               const char *key);
-
-/**
- * Get the first element of a list.
- *
- * Example of use:
- * for (config = ec_config_list_iter(list);
- *     config != NULL;
- *     config = ec_config_list_next(list, config)) {
- *             ...
- * }
- *
- * @param list
- *   The list configuration to iterate.
- * @return
- *   The first configuration element, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_list_first(struct ec_config *list);
-
-/**
- * Get next element in list.
- *
- * @param list
- *   The list configuration beeing iterated.
- * @param config
- *   The current configuration element.
- * @return
- *   The next configuration element, or NULL if there is no more element.
- */
-struct ec_config *
-ec_config_list_next(struct ec_config *list, struct ec_config *config);
-
-/**
- * Free a configuration.
- *
- * @param config
- *   The element to free.
- */
-void ec_config_free(struct ec_config *config);
-
-/**
- * Compare two configurations.
- *
- * @return
- *   0 if the configurations are equal, else -1.
- */
-int ec_config_cmp(const struct ec_config *value1,
-               const struct ec_config *value2);
-
-/**
- * Duplicate a configuration.
- *
- * @param config
- *   The configuration to duplicate.
- * @return
- *   The duplicated configuration, or NULL on error (errno is set).
- */
-struct ec_config *
-ec_config_dup(const struct ec_config *config);
-
-/**
- * Dump a configuration.
- *
- * @param out
- *   Output stream on which the dump will be sent.
- * @param config
- *   The configuration to dump.
- */
-void ec_config_dump(FILE *out, const struct ec_config *config);
-
-#endif
diff --git a/lib/ecoli_init.c b/lib/ecoli_init.c
deleted file mode 100644 (file)
index fd5c0c3..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-#include <ecoli_init.h>
-
-static struct ec_init_list init_list = TAILQ_HEAD_INITIALIZER(init_list);
-
-/* register an init function */
-void ec_init_register(struct ec_init *init)
-{
-       struct ec_init *cur;
-
-       if (TAILQ_EMPTY(&init_list)) {
-               TAILQ_INSERT_HEAD(&init_list, init, next);
-               return;
-       }
-
-
-       TAILQ_FOREACH(cur, &init_list, next) {
-               if (init->priority > cur->priority)
-                       continue;
-
-               TAILQ_INSERT_BEFORE(cur, init, next);
-               return;
-       }
-
-       TAILQ_INSERT_TAIL(&init_list, init, next);
-}
-
-int ec_init(void)
-{
-       struct ec_init *init;
-
-       TAILQ_FOREACH(init, &init_list, next) {
-               if (init->init() < 0)
-                       return -1;
-       }
-
-       return 0;
-}
diff --git a/lib/ecoli_init.h b/lib/ecoli_init.h
deleted file mode 100644 (file)
index 4e8bc1e..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Register initialization routines.
- */
-
-#ifndef ECOLI_INIT_
-#define ECOLI_INIT_
-
-#include <sys/queue.h>
-
-#include <ecoli_log.h>
-#include <ecoli_node.h>
-
-#define EC_INIT_REGISTER(t)                                            \
-       static void ec_init_init_##t(void);                             \
-       static void __attribute__((constructor, used))                  \
-       ec_init_init_##t(void)                                          \
-       {                                                               \
-                ec_init_register(&t);                                  \
-       }
-
-/**
- * Type of init function. Return 0 on success, -1 on error.
- */
-typedef int (ec_init_t)(void);
-
-TAILQ_HEAD(ec_init_list, ec_init);
-
-/**
- * A structure describing a test case.
- */
-struct ec_init {
-       TAILQ_ENTRY(ec_init) next;  /**< Next in list. */
-       ec_init_t *init;            /**< Init function. */
-       unsigned int priority;      /**< Priority (0=first, 99=last) */
-};
-
-/**
- * Register an initialization function.
- *
- * @param init
- *   A pointer to a ec_init structure to be registered.
- */
-void ec_init_register(struct ec_init *test);
-
-/**
- * Initialize ecoli library
- *
- * Must be called before any other function from libecoli, except
- * ec_malloc_register().
- *
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_init(void);
-
-#endif
diff --git a/lib/ecoli_keyval.c b/lib/ecoli_keyval.c
deleted file mode 100644 (file)
index 12fe66b..0000000
+++ /dev/null
@@ -1,569 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/queue.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <unistd.h>
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-
-#include <ecoli_init.h>
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_murmurhash.h>
-#include <ecoli_keyval.h>
-
-#define FACTOR 3
-
-EC_LOG_TYPE_REGISTER(keyval);
-
-static uint32_t ec_keyval_seed;
-
-struct ec_keyval_elt {
-       char *key;
-       void *val;
-       uint32_t hash;
-       ec_keyval_elt_free_t free;
-       unsigned int refcount;
-};
-
-struct ec_keyval_elt_ref {
-       LIST_ENTRY(ec_keyval_elt_ref) next;
-       struct ec_keyval_elt *elt;
-};
-
-LIST_HEAD(ec_keyval_elt_ref_list, ec_keyval_elt_ref);
-
-struct ec_keyval {
-       size_t len;
-       size_t table_size;
-       struct ec_keyval_elt_ref_list *table;
-};
-
-struct ec_keyval_iter {
-       const struct ec_keyval *keyval;
-       size_t cur_idx;
-       const struct ec_keyval_elt_ref *cur_ref;
-};
-
-struct ec_keyval *ec_keyval(void)
-{
-       struct ec_keyval *keyval;
-
-       keyval = ec_calloc(1, sizeof(*keyval));
-       if (keyval == NULL)
-               return NULL;
-
-       return keyval;
-}
-
-static struct ec_keyval_elt_ref *
-ec_keyval_lookup(const struct ec_keyval *keyval, const char *key)
-{
-       struct ec_keyval_elt_ref *ref;
-       uint32_t h, mask = keyval->table_size - 1;
-
-       if (keyval == NULL || key == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-       if (keyval->table_size == 0) {
-               errno = ENOENT;
-               return NULL;
-       }
-
-       h = ec_murmurhash3(key, strlen(key), ec_keyval_seed);
-       LIST_FOREACH(ref, &keyval->table[h & mask], next) {
-               if (strcmp(ref->elt->key, key) == 0)
-                       return ref;
-       }
-
-       errno = ENOENT;
-       return NULL;
-}
-
-static void ec_keyval_elt_ref_free(struct ec_keyval_elt_ref *ref)
-{
-       struct ec_keyval_elt *elt;
-
-       if (ref == NULL)
-               return;
-
-       elt = ref->elt;
-       if (elt != NULL && --elt->refcount == 0) {
-               ec_free(elt->key);
-               if (elt->free != NULL)
-                       elt->free(elt->val);
-               ec_free(elt);
-       }
-       ec_free(ref);
-}
-
-bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key)
-{
-       return !!ec_keyval_lookup(keyval, key);
-}
-
-void *ec_keyval_get(const struct ec_keyval *keyval, const char *key)
-{
-       struct ec_keyval_elt_ref *ref;
-
-       ref = ec_keyval_lookup(keyval, key);
-       if (ref == NULL)
-               return NULL;
-
-       return ref->elt->val;
-}
-
-int ec_keyval_del(struct ec_keyval *keyval, const char *key)
-{
-       struct ec_keyval_elt_ref *ref;
-
-       ref = ec_keyval_lookup(keyval, key);
-       if (ref == NULL)
-               return -1;
-
-       /* we could resize table here */
-
-       LIST_REMOVE(ref, next);
-       ec_keyval_elt_ref_free(ref);
-       keyval->len--;
-
-       return 0;
-}
-
-static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size)
-{
-       struct ec_keyval_elt_ref_list *new_table;
-       struct ec_keyval_elt_ref *ref;
-       size_t i;
-
-       if (new_size == 0 || (new_size & (new_size - 1))) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       new_table = ec_calloc(new_size, sizeof(*keyval->table));
-       if (new_table == NULL)
-               return -1;
-
-       for (i = 0; i < keyval->table_size; i++) {
-               while (!LIST_EMPTY(&keyval->table[i])) {
-                       ref = LIST_FIRST(&keyval->table[i]);
-                       LIST_REMOVE(ref, next);
-                       LIST_INSERT_HEAD(
-                               &new_table[ref->elt->hash & (new_size - 1)],
-                               ref, next);
-               }
-       }
-
-       ec_free(keyval->table);
-       keyval->table = new_table;
-       keyval->table_size = new_size;
-
-       return 0;
-}
-
-static int
-__ec_keyval_set(struct ec_keyval *keyval, struct ec_keyval_elt_ref *ref)
-{
-       size_t new_size;
-       uint32_t mask;
-       int ret;
-
-       /* remove previous entry if any */
-       ec_keyval_del(keyval, ref->elt->key);
-
-       if (keyval->len >= keyval->table_size) {
-               if (keyval->table_size != 0)
-                       new_size =  keyval->table_size << FACTOR;
-               else
-                       new_size =  1 << FACTOR;
-               ret = ec_keyval_table_resize(keyval, new_size);
-               if (ret < 0)
-                       return ret;
-       }
-
-       mask = keyval->table_size - 1;
-       LIST_INSERT_HEAD(&keyval->table[ref->elt->hash & mask], ref, next);
-       keyval->len++;
-
-       return 0;
-}
-
-int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val,
-       ec_keyval_elt_free_t free_cb)
-{
-       struct ec_keyval_elt *elt = NULL;
-       struct ec_keyval_elt_ref *ref = NULL;
-       uint32_t h;
-
-       if (keyval == NULL || key == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       ref = ec_calloc(1, sizeof(*ref));
-       if (ref == NULL)
-               goto fail;
-
-       elt = ec_calloc(1, sizeof(*elt));
-       if (elt == NULL)
-               goto fail;
-
-       ref->elt = elt;
-       elt->refcount = 1;
-       elt->val = val;
-       val = NULL;
-       elt->free = free_cb;
-       elt->key = ec_strdup(key);
-       if (elt->key == NULL)
-               goto fail;
-       h = ec_murmurhash3(key, strlen(key), ec_keyval_seed);
-       elt->hash = h;
-
-       if (__ec_keyval_set(keyval, ref) < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       if (free_cb != NULL && val != NULL)
-               free_cb(val);
-       ec_keyval_elt_ref_free(ref);
-       return -1;
-}
-
-void ec_keyval_free(struct ec_keyval *keyval)
-{
-       struct ec_keyval_elt_ref *ref;
-       size_t i;
-
-       if (keyval == NULL)
-               return;
-
-       for (i = 0; i < keyval->table_size; i++) {
-               while (!LIST_EMPTY(&keyval->table[i])) {
-                       ref = LIST_FIRST(&keyval->table[i]);
-                       LIST_REMOVE(ref, next);
-                       ec_keyval_elt_ref_free(ref);
-               }
-       }
-       ec_free(keyval->table);
-       ec_free(keyval);
-}
-
-size_t ec_keyval_len(const struct ec_keyval *keyval)
-{
-       return keyval->len;
-}
-
-void
-ec_keyval_iter_free(struct ec_keyval_iter *iter)
-{
-       ec_free(iter);
-}
-
-struct ec_keyval_iter *
-ec_keyval_iter(const struct ec_keyval *keyval)
-{
-       struct ec_keyval_iter *iter = NULL;
-
-       iter = ec_calloc(1, sizeof(*iter));
-       if (iter == NULL)
-               return NULL;
-
-       iter->keyval = keyval;
-       iter->cur_idx = 0;
-       iter->cur_ref = NULL;
-
-       ec_keyval_iter_next(iter);
-
-       return iter;
-}
-
-void
-ec_keyval_iter_next(struct ec_keyval_iter *iter)
-{
-       const struct ec_keyval_elt_ref *ref;
-       size_t i;
-
-       i = iter->cur_idx;
-       if (i == iter->keyval->table_size)
-               return; /* no more element */
-
-       if (iter->cur_ref != NULL) {
-               ref = LIST_NEXT(iter->cur_ref, next);
-               if (ref != NULL) {
-                       iter->cur_ref = ref;
-                       return;
-               }
-               i++;
-       }
-
-       for (; i < iter->keyval->table_size; i++) {
-               LIST_FOREACH(ref, &iter->keyval->table[i], next) {
-                       iter->cur_idx = i;
-                       iter->cur_ref = LIST_FIRST(&iter->keyval->table[i]);
-                       return;
-               }
-       }
-
-       iter->cur_idx = iter->keyval->table_size;
-       iter->cur_ref = NULL;
-}
-
-bool
-ec_keyval_iter_valid(const struct ec_keyval_iter *iter)
-{
-       if (iter == NULL || iter->cur_ref == NULL)
-               return false;
-
-       return true;
-}
-
-const char *
-ec_keyval_iter_get_key(const struct ec_keyval_iter *iter)
-{
-       if (iter == NULL || iter->cur_ref == NULL)
-               return NULL;
-
-       return iter->cur_ref->elt->key;
-}
-
-void *
-ec_keyval_iter_get_val(const struct ec_keyval_iter *iter)
-{
-       if (iter == NULL || iter->cur_ref == NULL)
-               return NULL;
-
-       return iter->cur_ref->elt->val;
-}
-
-void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval)
-{
-       struct ec_keyval_iter *iter;
-
-       if (keyval == NULL) {
-               fprintf(out, "empty keyval\n");
-               return;
-       }
-
-       fprintf(out, "keyval:\n");
-       for (iter = ec_keyval_iter(keyval);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               fprintf(out, "  %s: %p\n",
-                       ec_keyval_iter_get_key(iter),
-                       ec_keyval_iter_get_val(iter));
-       }
-       ec_keyval_iter_free(iter);
-}
-
-struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval)
-{
-       struct ec_keyval *dup = NULL;
-       struct ec_keyval_elt_ref *ref, *dup_ref = NULL;
-       size_t i;
-
-       dup = ec_keyval();
-       if (dup == NULL)
-               return NULL;
-
-       for (i = 0; i < keyval->table_size; i++) {
-               LIST_FOREACH(ref, &keyval->table[i], next) {
-                       dup_ref = ec_calloc(1, sizeof(*ref));
-                       if (dup_ref == NULL)
-                               goto fail;
-                       dup_ref->elt = ref->elt;
-                       ref->elt->refcount++;
-
-                       if (__ec_keyval_set(dup, dup_ref) < 0)
-                               goto fail;
-               }
-       }
-
-       return dup;
-
-fail:
-       ec_keyval_elt_ref_free(dup_ref);
-       ec_keyval_free(dup);
-       return NULL;
-}
-
-static int ec_keyval_init_func(void)
-{
-       int fd;
-       ssize_t ret;
-
-       return 0;//XXX for test reproduceability
-
-       fd = open("/dev/urandom", 0);
-       if (fd == -1) {
-               fprintf(stderr, "failed to open /dev/urandom\n");
-               return -1;
-       }
-       ret = read(fd, &ec_keyval_seed, sizeof(ec_keyval_seed));
-       if (ret != sizeof(ec_keyval_seed)) {
-               fprintf(stderr, "failed to read /dev/urandom\n");
-               return -1;
-       }
-       close(fd);
-       return 0;
-}
-
-static struct ec_init ec_keyval_init = {
-       .init = ec_keyval_init_func,
-       .priority = 50,
-};
-
-EC_INIT_REGISTER(ec_keyval_init);
-
-/* LCOV_EXCL_START */
-static int ec_keyval_testcase(void)
-{
-       struct ec_keyval *keyval, *dup;
-       struct ec_keyval_iter *iter;
-       char *val;
-       size_t i, count;
-       int ret, testres = 0;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-
-       keyval = ec_keyval();
-       if (keyval == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create keyval\n");
-               return -1;
-       }
-
-       count = 0;
-       for (iter = ec_keyval_iter(keyval);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               count++;
-       }
-       ec_keyval_iter_free(iter);
-       testres |= EC_TEST_CHECK(count == 0, "invalid count in iterator");
-
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0, "bad keyval len");
-       ret = ec_keyval_set(keyval, "key1", "val1", NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       ret = ec_keyval_set(keyval, "key2", ec_strdup("val2"), ec_free_func);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2, "bad keyval len");
-
-       val = ec_keyval_get(keyval, "key1");
-       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val1"),
-                               "invalid keyval value");
-       val = ec_keyval_get(keyval, "key2");
-       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val2"),
-                               "invalid keyval value");
-       val = ec_keyval_get(keyval, "key3");
-       testres |= EC_TEST_CHECK(val == NULL, "key3 should be NULL");
-
-       ret = ec_keyval_set(keyval, "key1", "another_val1", NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       ret = ec_keyval_set(keyval, "key2", ec_strdup("another_val2"),
-                       ec_free_func);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2,
-                               "bad keyval len");
-
-       val = ec_keyval_get(keyval, "key1");
-       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val1"),
-               "invalid keyval value");
-       val = ec_keyval_get(keyval, "key2");
-       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val2"),
-               "invalid keyval value");
-       testres |= EC_TEST_CHECK(ec_keyval_has_key(keyval, "key1"),
-               "key1 should be in keyval");
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_keyval_dump(f, NULL);
-       fclose(f);
-       f = NULL;
-       free(buf);
-       buf = NULL;
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_keyval_dump(f, keyval);
-       fclose(f);
-       f = NULL;
-       free(buf);
-       buf = NULL;
-
-       ret = ec_keyval_del(keyval, "key1");
-       testres |= EC_TEST_CHECK(ret == 0, "cannot del key1");
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 1,
-               "invalid keyval len");
-       ret = ec_keyval_del(keyval, "key2");
-       testres |= EC_TEST_CHECK(ret == 0, "cannot del key2");
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0,
-               "invalid keyval len");
-
-       for (i = 0; i < 100; i++) {
-               char key[8];
-               snprintf(key, sizeof(key), "k%zd", i);
-               ret = ec_keyval_set(keyval, key, "val", NULL);
-               testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       }
-       dup = ec_keyval_dup(keyval);
-       testres |= EC_TEST_CHECK(dup != NULL, "cannot duplicate keyval");
-       if (dup != NULL) {
-               for (i = 0; i < 100; i++) {
-                       char key[8];
-                       snprintf(key, sizeof(key), "k%zd", i);
-                       val = ec_keyval_get(dup, key);
-                       testres |= EC_TEST_CHECK(
-                               val != NULL && !strcmp(val, "val"),
-                               "invalid keyval value");
-               }
-               ec_keyval_free(dup);
-               dup = NULL;
-       }
-
-       count = 0;
-       for (iter = ec_keyval_iter(keyval);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               count++;
-       }
-       ec_keyval_iter_free(iter);
-       testres |= EC_TEST_CHECK(count == 100, "invalid count in iterator");
-
-       /* einval */
-       ret = ec_keyval_set(keyval, NULL, "val1", NULL);
-       testres |= EC_TEST_CHECK(ret == -1, "should not be able to set key");
-       val = ec_keyval_get(keyval, NULL);
-       testres |= EC_TEST_CHECK(val == NULL, "get(NULL) should no success");
-
-       ec_keyval_free(keyval);
-
-       return testres;
-
-fail:
-       ec_keyval_free(keyval);
-       if (f)
-               fclose(f);
-       free(buf);
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_keyval_test = {
-       .name = "keyval",
-       .test = ec_keyval_testcase,
-};
-
-EC_TEST_REGISTER(ec_keyval_test);
diff --git a/lib/ecoli_keyval.h b/lib/ecoli_keyval.h
deleted file mode 100644 (file)
index a3e9400..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Simple hash table API
- *
- * This file provides functions to store objects in hash tables, using strings
- * as keys.
- */
-
-#ifndef ECOLI_KEYVAL_
-#define ECOLI_KEYVAL_
-
-#include <stdio.h>
-#include <stdbool.h>
-
-typedef void (*ec_keyval_elt_free_t)(void *);
-
-struct ec_keyval;
-struct ec_keyval_iter;
-
-/**
- * Create a hash table.
- *
- * @return
- *   The hash table, or NULL on error (errno is set).
- */
-struct ec_keyval *ec_keyval(void);
-
-/**
- * Get a value from the hash table.
- *
- * @param keyval
- *   The hash table.
- * @param key
- *   The key string.
- * @return
- *   The element if it is found, or NULL on error (errno is set).
- *   In case of success but the element is NULL, errno is set to 0.
- */
-void *ec_keyval_get(const struct ec_keyval *keyval, const char *key);
-
-/**
- * Check if the hash table contains this key.
- *
- * @param keyval
- *   The hash table.
- * @param key
- *   The key string.
- * @return
- *   true if it contains the key, else false.
- */
-bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key);
-
-/**
- * Delete an object from the hash table.
- *
- * @param keyval
- *   The hash table.
- * @param key
- *   The key string.
- * @return
- *   0 on success, or -1 on error (errno is set).
- */
-int ec_keyval_del(struct ec_keyval *keyval, const char *key);
-
-/**
- * Add/replace an object in the hash table.
- *
- * @param keyval
- *   The hash table.
- * @param key
- *   The key string.
- * @param val
- *   The pointer to be saved in the hash table.
- * @param free_cb
- *   An optional pointer to a destructor function called when an
- *   object is destroyed (ec_keyval_del() or ec_keyval_free()).
- * @return
- *   0 on success, or -1 on error (errno is set).
- *   On error, the passed value is freed (free_cb(val) is called).
- */
-int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val,
-       ec_keyval_elt_free_t free_cb);
-
-/**
- * Free a hash table an all its objects.
- *
- * @param keyval
- *   The hash table.
- */
-void ec_keyval_free(struct ec_keyval *keyval);
-
-/**
- * Get the length of a hash table.
- *
- * @param keyval
- *   The hash table.
- * @return
- *   The length of the hash table.
- */
-size_t ec_keyval_len(const struct ec_keyval *keyval);
-
-/**
- * Duplicate a hash table
- *
- * A reference counter is shared between the clones of
- * hash tables so that the objects are freed only when
- * the last reference is destroyed.
- *
- * @param keyval
- *   The hash table.
- * @return
- *   The duplicated hash table, or NULL on error (errno is set).
- */
-struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval);
-
-/**
- * Dump a hash table.
- *
- * @param out
- *   The stream where the dump is sent.
- * @param keyval
- *   The hash table.
- */
-void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval);
-
-/**
- * Iterate the elements in the hash table.
- *
- * The typical usage is as below:
- *
- *     // dump elements
- *     for (iter = ec_keyval_iter(keyval);
- *          ec_keyval_iter_valid(iter);
- *          ec_keyval_iter_next(iter)) {
- *             printf("  %s: %p\n",
- *                     ec_keyval_iter_get_key(iter),
- *                     ec_keyval_iter_get_val(iter));
- *     }
- *     ec_keyval_iter_free(iter);
- *
- * @param keyval
- *   The hash table.
- * @return
- *   An iterator, or NULL on error (errno is set).
- */
-struct ec_keyval_iter *
-ec_keyval_iter(const struct ec_keyval *keyval);
-
-/**
- * Make the iterator point to the next element in the hash table.
- *
- * @param iter
- *   The hash table iterator.
- */
-void ec_keyval_iter_next(struct ec_keyval_iter *iter);
-
-/**
- * Free the iterator.
- *
- * @param iter
- *   The hash table iterator.
- */
-void ec_keyval_iter_free(struct ec_keyval_iter *iter);
-
-/**
- * Check if the iterator points to a valid element.
- *
- * @param iter
- *   The hash table iterator.
- * @return
- *   true if the element is valid, else false.
- */
-bool
-ec_keyval_iter_valid(const struct ec_keyval_iter *iter);
-
-/**
- * Get the key of the current element.
- *
- * @param iter
- *   The hash table iterator.
- * @return
- *   The current element key, or NULL if the iterator points to an
- *   invalid element.
- */
-const char *
-ec_keyval_iter_get_key(const struct ec_keyval_iter *iter);
-
-/**
- * Get the value of the current element.
- *
- * @param iter
- *   The hash table iterator.
- * @return
- *   The current element value, or NULL if the iterator points to an
- *   invalid element.
- */
-void *
-ec_keyval_iter_get_val(const struct ec_keyval_iter *iter);
-
-
-#endif
diff --git a/lib/ecoli_log.c b/lib/ecoli_log.c
deleted file mode 100644 (file)
index e7577cd..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#define _GNU_SOURCE /* for vasprintf */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <syslog.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-
-EC_LOG_TYPE_REGISTER(log);
-
-static ec_log_t ec_log_fct = ec_log_default_cb;
-static void *ec_log_opaque;
-
-struct ec_log_type {
-       char *name;
-       enum ec_log_level level;
-};
-
-static struct ec_log_type *log_types;
-static size_t log_types_len;
-static enum ec_log_level global_level = EC_LOG_WARNING;
-
-int ec_log_level_set(enum ec_log_level level)
-{
-       if (level > EC_LOG_DEBUG)
-               return -1;
-       global_level = level;
-
-       return 0;
-}
-
-enum ec_log_level ec_log_level_get(void)
-{
-       return global_level;
-}
-
-int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
-               const char *str)
-{
-       (void)opaque;
-
-       if (level > ec_log_level_get())
-               return 0;
-
-       if (fprintf(stderr, "[%d] %-12s %s", level, ec_log_name(type), str) < 0)
-               return -1;
-
-       return 0;
-}
-
-int ec_log_fct_register(ec_log_t usr_log, void *opaque)
-{
-       if (usr_log == NULL) {
-               ec_log_fct = ec_log_default_cb;
-               ec_log_opaque = NULL;
-       } else {
-               ec_log_fct = usr_log;
-               ec_log_opaque = opaque;
-       }
-
-       return 0;
-}
-
-static int
-ec_log_lookup(const char *name)
-{
-       size_t i;
-
-       for (i = 0; i < log_types_len; i++) {
-               if (log_types[i].name == NULL)
-                       continue;
-               if (strcmp(name, log_types[i].name) == 0)
-                       return i;
-       }
-
-       return -1;
-}
-
-int
-ec_log_type_register(const char *name)
-{
-       struct ec_log_type *new_types;
-       char *copy;
-       int id;
-
-       id = ec_log_lookup(name);
-       if (id >= 0)
-               return id;
-
-       new_types = ec_realloc(log_types,
-               sizeof(*new_types) * (log_types_len + 1));
-       if (new_types == NULL)
-               return -1; /* errno is set */
-       log_types = new_types;
-
-       copy = ec_strdup(name);
-       if (copy == NULL)
-               return -1; /* errno is set */
-
-       id = log_types_len++;
-       log_types[id].name = copy;
-       log_types[id].level = EC_LOG_DEBUG;
-
-       return id;
-}
-
-const char *
-ec_log_name(int type)
-{
-       if (type < 0 || (unsigned int)type >= log_types_len)
-               return "unknown";
-       return log_types[type].name;
-}
-
-int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap)
-{
-       char *s;
-       int ret;
-
-       /* don't use ec_vasprintf here, because it will call
-        * ec_malloc(), then ec_log(), ec_vasprintf()...
-        * -> stack overflow */
-       ret = vasprintf(&s, format, ap);
-       if (ret < 0)
-               return ret;
-
-       ret = ec_log_fct(type, level, ec_log_opaque, s);
-       free(s);
-
-       return ret;
-}
-
-int ec_log(int type, enum ec_log_level level, const char *format, ...)
-{
-       va_list ap;
-       int ret;
-
-       va_start(ap, format);
-       ret = ec_vlog(type, level, format, ap);
-       va_end(ap);
-
-       return ret;
-}
-
-/* LCOV_EXCL_START */
-static int
-log_cb(int type, enum ec_log_level level, void *opaque, const char *str)
-{
-       (void)type;
-       (void)level;
-       (void)str;
-       *(int *)opaque = 1;
-
-       return 0;
-}
-
-static int ec_log_testcase(void)
-{
-       ec_log_t prev_log_cb;
-       void *prev_opaque;
-       const char *logname;
-       int testres = 0;
-       int check_cb = 0;
-       int logtype;
-       int level;
-       int ret;
-
-       prev_log_cb = ec_log_fct;
-       prev_opaque = ec_log_opaque;
-
-       ret = ec_log_fct_register(log_cb, &check_cb);
-       testres |= EC_TEST_CHECK(ret == 0,
-                               "cannot register log function\n");
-       EC_LOG(LOG_ERR, "test\n");
-       testres |= EC_TEST_CHECK(check_cb == 1,
-                               "log callback was not invoked\n");
-       logtype = ec_log_lookup("dsdedesdes");
-       testres |= EC_TEST_CHECK(logtype == -1,
-                               "lookup invalid name should return -1");
-       logtype = ec_log_lookup("log");
-       logname = ec_log_name(logtype);
-       testres |= EC_TEST_CHECK(logname != NULL &&
-                               !strcmp(logname, "log"),
-                               "cannot get log name\n");
-       logname = ec_log_name(-1);
-       testres |= EC_TEST_CHECK(logname != NULL &&
-                               !strcmp(logname, "unknown"),
-                               "cannot get invalid log name\n");
-       logname = ec_log_name(34324);
-       testres |= EC_TEST_CHECK(logname != NULL &&
-                               !strcmp(logname, "unknown"),
-                               "cannot get invalid log name\n");
-       level = ec_log_level_get();
-       ret = ec_log_level_set(2);
-       testres |= EC_TEST_CHECK(ret == 0 && ec_log_level_get() == 2,
-                               "cannot set log level\n");
-       ret = ec_log_level_set(10);
-       testres |= EC_TEST_CHECK(ret != 0,
-                               "should not be able to set log level\n");
-
-       ret = ec_log_fct_register(NULL, NULL);
-       ec_log_level_set(LOG_DEBUG);
-       EC_LOG(LOG_DEBUG, "test log\n");
-       ec_log_level_set(LOG_INFO);
-       EC_LOG(LOG_DEBUG, "test log (not displayed)\n");
-       ec_log_level_set(level);
-
-       ec_log_fct = prev_log_cb;
-       ec_log_opaque = prev_opaque;
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_log_test = {
-       .name = "log",
-       .test = ec_log_testcase,
-};
-
-EC_TEST_REGISTER(ec_log_test);
diff --git a/lib/ecoli_log.h b/lib/ecoli_log.h
deleted file mode 100644 (file)
index be7e380..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Logging API
- *
- * This file provide logging helpers:
- * - logging functions, supporting printf-like format
- * - several debug level (similar to syslog)
- * - named log types
- * - redirection of log to a user functions (default logs nothing)
- */
-
-#ifndef ECOLI_LOG_
-#define ECOLI_LOG_
-
-#include <stdarg.h>
-
-#include <ecoli_assert.h>
-
-enum ec_log_level {
-       EC_LOG_EMERG   = 0,  /* system is unusable               */
-       EC_LOG_ALERT   = 1,  /* action must be taken immediately */
-       EC_LOG_CRIT    = 2,  /* critical conditions              */
-       EC_LOG_ERR     = 3,  /* error conditions                 */
-       EC_LOG_WARNING = 4,  /* warning conditions               */
-       EC_LOG_NOTICE  = 5,  /* normal but significant condition */
-       EC_LOG_INFO    = 6,  /* informational                    */
-       EC_LOG_DEBUG   = 7,  /* debug-level messages             */
-};
-
-/**
- * Register a log type.
- *
- * This macro defines a function that will be called at startup (using
- * the "constructor" attribute). This function register the named type
- * passed as argument, and sets a static global variable
- * "ec_log_local_type". This variable is used as the default log type
- * for this file when using EC_LOG() or EC_VLOG().
- *
- * This macro can be present several times in a file. In this case, the
- * local log type is set to the last registered type.
- *
- * On error, the function aborts.
- *
- * @param name
- *   The name of the log to be registered.
- */
-#define EC_LOG_TYPE_REGISTER(name)                                     \
-       static int name##_log_type;                                     \
-       static int ec_log_local_type;                                   \
-       __attribute__((constructor, used))                              \
-       static void ec_log_register_##name(void)                        \
-       {                                                               \
-               ec_log_local_type = ec_log_type_register(#name);        \
-               ec_assert_print(ec_log_local_type >= 0,                 \
-                               "cannot register log type.\n");         \
-               name##_log_type = ec_log_local_type;                    \
-       }
-
-/**
- * User log function type.
- *
- * It is advised that a user-defined log function drops all messages
- * that are at least as critical as ec_log_level_get(), as done by the
- * default handler.
- *
- * @param type
- *   The log type identifier.
- * @param level
- *   The log level.
- * @param opaque
- *   The opaque pointer that was passed to ec_log_fct_register().
- * @param str
- *   The string to log.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-typedef int (*ec_log_t)(int type, enum ec_log_level level, void *opaque,
-                       const char *str);
-
-/**
- * Register a user log function.
- *
- * @param usr_log
- *   Function pointer that will be invoked for each log call.
- *   If the parameter is NULL, ec_log_default_cb() is used.
- * @param opaque
- *   Opaque pointer passed to the log function.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_log_fct_register(ec_log_t usr_log, void *opaque);
-
-/**
- * Register a named log type.
- *
- * Register a new log type, which is identified by its name. The
- * function returns a log identifier associated to the log name. If the
- * name is already registered, the function just returns its identifier.
- *
- * @param name
- *   The name of the log type.
- * @return
- *   The log type identifier on success (positive or zero), -1 on
- *   error (errno is set).
- */
-int ec_log_type_register(const char *name);
-
-/**
- * Return the log name associated to the log type identifier.
- *
- * @param type
- *   The log type identifier.
- * @return
- *   The name associated to the log type, or "unknown". It always return
- *   a valid string (never NULL).
- */
-const char *ec_log_name(int type);
-
-/**
- * Log a formatted string.
- *
- * @param type
- *   The log type identifier.
- * @param level
- *   The log level.
- * @param format
- *   The format string, followed by optional arguments.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_log(int type, enum ec_log_level level, const char *format, ...)
-       __attribute__((format(__printf__, 3, 4)));
-
-/**
- * Log a formatted string.
- *
- * @param type
- *   The log type identifier.
- * @param level
- *   The log level.
- * @param format
- *   The format string.
- * @param ap
- *   The list of arguments.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap);
-
-/**
- * Log a formatted string using the local log type.
- *
- * This macro requires that a log type is previously register with
- * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type"
- * variable.
- *
- * @param level
- *   The log level.
- * @param format
- *   The format string, followed by optional arguments.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-#define EC_LOG(level, args...) ec_log(ec_log_local_type, level, args)
-
-/**
- * Log a formatted string using the local log type.
- *
- * This macro requires that a log type is previously register with
- * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type"
- * variable.
- *
- * @param level
- *   The log level.
- * @param format
- *   The format string.
- * @param ap
- *   The list of arguments.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-#define EC_VLOG(level, fmt, ap) ec_vlog(ec_log_local_type, level, fmt, ap)
-
-/**
- * Default log handler.
- *
- * This is the default log function that is used by the library. By
- * default, it prints all logs whose level is WARNING or more critical.
- * This level can be changed with ec_log_level_set().
- *
- * @param type
- *   The log type identifier.
- * @param level
- *   The log level.
- * @param opaque
- *   Unused.
- * @param str
- *   The string to be logged.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
-               const char *str);
-
-/**
- * Set the global log level.
- *
- * This level is used by the default log handler, ec_log_default_cb().
- * All messages that are at least as critical as the default level are
- * displayed.
- *
- * It is advised
- *
- * @param level
- *   The log level to be set.
- * @return
- *   0 on success, -1 on error.
- */
-int ec_log_level_set(enum ec_log_level level);
-
-/**
- * Get the global log level.
- *
- * This level is used by the default log handler, ec_log_default_cb().
- * All messages that are at least as critical as the default level are
- * displayed.
- *
- * @param level
- *   The log level to be set.
- * @return
- *   0 on success, -1 on error.
- */
-enum ec_log_level ec_log_level_get(void);
-
-#endif
diff --git a/lib/ecoli_malloc.c b/lib/ecoli_malloc.c
deleted file mode 100644 (file)
index 505f49f..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <ecoli_init.h>
-#include <ecoli_test.h>
-#include <ecoli_malloc.h>
-
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-EC_LOG_TYPE_REGISTER(malloc);
-
-static int init_done = 0;
-
-struct ec_malloc_handler ec_malloc_handler;
-
-int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free,
-       ec_realloc_t usr_realloc)
-{
-       if (usr_malloc == NULL || usr_free == NULL || usr_realloc == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (init_done) {
-               errno = EBUSY;
-               return -1;
-       }
-
-       ec_malloc_handler.malloc = usr_malloc;
-       ec_malloc_handler.free = usr_free;
-       ec_malloc_handler.realloc = usr_realloc;
-
-       return 0;
-}
-
-void *__ec_malloc(size_t size, const char *file, unsigned int line)
-{
-       return ec_malloc_handler.malloc(size, file, line);
-}
-
-void *ec_malloc_func(size_t size)
-{
-       return __ec_malloc(size, __FILE__, __LINE__);
-}
-
-void __ec_free(void *ptr, const char *file, unsigned int line)
-{
-       ec_malloc_handler.free(ptr, file, line);
-}
-
-void ec_free_func(void *ptr)
-{
-       __ec_free(ptr, __FILE__, __LINE__);
-}
-
-void *__ec_calloc(size_t nmemb, size_t size, const char *file,
-       unsigned int line)
-{
-       void *ptr;
-       size_t total;
-
-       /* check overflow */
-       total = size * nmemb;
-       if (nmemb != 0 && size != (total / nmemb)) {
-               errno = ENOMEM;
-               return NULL;
-       }
-
-       ptr = __ec_malloc(total, file, line);
-       if (ptr == NULL)
-               return NULL;
-
-       memset(ptr, 0, total);
-       return ptr;
-}
-
-void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line)
-{
-       return ec_malloc_handler.realloc(ptr, size, file, line);
-}
-
-char *__ec_strdup(const char *s, const char *file, unsigned int line)
-{
-       size_t sz = strlen(s) + 1;
-       char *s2;
-
-       s2 = __ec_malloc(sz, file, line);
-       if (s2 == NULL)
-               return NULL;
-
-       memcpy(s2, s, sz);
-
-       return s2;
-}
-
-char *__ec_strndup(const char *s, size_t n, const char *file, unsigned int line)
-{
-       size_t sz = strnlen(s, n);
-       char *s2;
-
-       s2 = __ec_malloc(sz + 1, file, line);
-       if (s2 == NULL)
-               return NULL;
-
-       memcpy(s2, s, sz);
-       s2[sz] = '\0';
-
-       return s2;
-}
-
-static int ec_malloc_init_func(void)
-{
-       init_done = 1;
-       return 0;
-}
-
-static struct ec_init ec_malloc_init = {
-       .init = ec_malloc_init_func,
-       .priority = 40,
-};
-
-EC_INIT_REGISTER(ec_malloc_init);
-
-/* LCOV_EXCL_START */
-static int ec_malloc_testcase(void)
-{
-       int ret, testres = 0;
-       char *ptr, *ptr2;
-
-       ret = ec_malloc_register(NULL, NULL, NULL);
-       testres |= EC_TEST_CHECK(ret == -1,
-               "should not be able to register NULL malloc handlers");
-       ret = ec_malloc_register(__ec_malloc, __ec_free, __ec_realloc);
-       testres |= EC_TEST_CHECK(ret == -1,
-               "should not be able to register after init");
-
-       /* registration is tested in the test main.c */
-
-       ptr = ec_malloc(10);
-       if (ptr == NULL)
-               return -1;
-       memset(ptr, 0, 10);
-       ptr2 = ec_realloc(ptr, 20);
-       EC_TEST_CHECK(ptr2 != NULL, "cannot realloc ptr\n");
-       if (ptr2 == NULL)
-               ec_free(ptr);
-       else
-               ec_free(ptr2);
-       ptr = NULL;
-       ptr2 = NULL;
-
-       ptr = ec_malloc_func(10);
-       if (ptr == NULL)
-               return -1;
-       memset(ptr, 0, 10);
-       ec_free_func(ptr);
-       ptr = NULL;
-
-       ptr = ec_calloc(2, (size_t)-1);
-       EC_TEST_CHECK(ptr == NULL, "bad overflow check in ec_calloc\n");
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_malloc_test = {
-       .name = "malloc",
-       .test = ec_malloc_testcase,
-};
-
-EC_TEST_REGISTER(ec_malloc_test);
diff --git a/lib/ecoli_malloc.h b/lib/ecoli_malloc.h
deleted file mode 100644 (file)
index e80c3d9..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Interface to configure the allocator used by libecoli.
- * By default, the standard allocation functions from libc are used.
- */
-
-#ifndef ECOLI_MALLOC_
-#define ECOLI_MALLOC_
-
-#include <sys/types.h>
-#include <stdlib.h>
-#include <string.h>
-
-/**
- * Function type of malloc, passed to ec_malloc_register().
- *
- * The API is the same than malloc(), excepted the file and line
- * arguments.
- *
- * @param size
- *   The size of the memory area to allocate.
- * @param file
- *   The path to the file that invoked the malloc.
- * @param line
- *   The line in the file that invoked the malloc.
- * @return
- *   A pointer to the allocated memory area, or NULL on error (errno
- *   is set).
- */
-typedef void *(*ec_malloc_t)(size_t size, const char *file, unsigned int line);
-
-/**
- * Function type of free, passed to ec_malloc_register().
- *
- * The API is the same than free(), excepted the file and line
- * arguments.
- *
- * @param ptr
- *   The pointer to the memory area to be freed.
- * @param file
- *   The path to the file that invoked the malloc.
- * @param line
- *   The line in the file that invoked the malloc.
- */
-typedef void (*ec_free_t)(void *ptr, const char *file, unsigned int line);
-
-/**
- * Function type of realloc, passed to ec_malloc_register().
- *
- * The API is the same than realloc(), excepted the file and line
- * arguments.
- *
- * @param ptr
- *   The pointer to the memory area to be reallocated.
- * @param file
- *   The path to the file that invoked the malloc.
- * @param line
- *   The line in the file that invoked the malloc.
- * @return
- *   A pointer to the allocated memory area, or NULL on error (errno
- *   is set).
- */
-typedef void *(*ec_realloc_t)(void *ptr, size_t size, const char *file,
-       unsigned int line);
-
-/**
- * Register allocation functions.
- *
- * This function can be use to register another allocator
- * to be used by libecoli. By default, ec_malloc(), ec_free() and
- * ec_realloc() use the standard libc allocator. Another handler
- * can be used for debug purposes or when running in a specific
- * environment.
- *
- * This function must be called before ec_init().
- *
- * @param usr_malloc
- *   A user-defined malloc function.
- * @param usr_free
- *   A user-defined free function.
- * @param usr_realloc
- *   A user-defined realloc function.
- * @return
- *   0 on success, or -1 on error (errno is set).
- */
-int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free,
-       ec_realloc_t usr_realloc);
-
-struct ec_malloc_handler {
-       ec_malloc_t malloc;
-       ec_free_t free;
-       ec_realloc_t realloc;
-};
-
-extern struct ec_malloc_handler ec_malloc_handler;
-
-/**
- * Allocate a memory area.
- *
- * Like malloc(), ec_malloc() allocates size bytes and returns a pointer
- * to the allocated memory. The memory is not initialized. The memory is
- * freed with ec_free().
- *
- * @param size
- *   The size of the area to allocate in bytes.
- * @return
- *   The pointer to the allocated memory, or NULL on error (errno is set).
- */
-#define ec_malloc(size) ({                                             \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.malloc == NULL)                           \
-               ret_ = malloc(size);                                    \
-       else                                                            \
-               ret_ = __ec_malloc(size, __FILE__, __LINE__);           \
-       ret_;                                                           \
-       })
-
-/**
- * Ecoli malloc function.
- *
- * Use this function when the macro ec_malloc() cannot be used,
- * for instance when it is passed as a callback pointer.
- */
-void *ec_malloc_func(size_t size);
-
-/**
- * Free a memory area.
- *
- * Like free(), ec_free() frees the area pointed by ptr, which must have
- * been returned by a previous call to ec_malloc() or any other
- * allocation function of this file.
- *
- * @param ptr
- *   The pointer to the memory area.
- */
-#define ec_free(ptr) ({                                                        \
-       if (ec_malloc_handler.free == NULL)                             \
-               free(ptr);                                              \
-       else                                                            \
-               __ec_free(ptr, __FILE__, __LINE__);                     \
-       })
-
-/**
- * Ecoli free function.
- *
- * Use this function when the macro ec_free() cannot be used,
- * for instance when it is passed as a callback pointer.
- */
-void ec_free_func(void *ptr);
-
-/**
- * Resize an allocated memory area.
- *
- * @param ptr
- *   The pointer to the previously allocated memory area, or NULL.
- * @param size
- *   The new size of the memory area.
- * @return
- *   A pointer to the newly allocated memory, or NULL if the request
- *   fails. In that case, the original area is left untouched.
- */
-#define ec_realloc(ptr, size) ({                                       \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.realloc == NULL)                          \
-               ret_ = realloc(ptr, size);                              \
-       else                                                            \
-               ret_ = __ec_realloc(ptr, size, __FILE__, __LINE__);     \
-       ret_;                                                           \
-       })
-
-/**
- * Allocate and initialize an array of elements.
- *
- * @param n
- *   The number of elements.
- * @param size
- *   The size of each element.
- * @return
- *   The pointer to the allocated memory, or NULL on error (errno is set).
- */
-#define ec_calloc(n, size) ({                                          \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.malloc == NULL)                           \
-               ret_ = calloc(n, size);                                 \
-       else                                                            \
-               ret_ = __ec_calloc(n, size, __FILE__, __LINE__);        \
-       ret_;                                                           \
-       })
-
-/**
- * Duplicate a string.
- *
- * Memory for the new string is obtained with ec_malloc(), and can be
- * freed with ec_free().
- *
- * @param s
- *   The string to be duplicated.
- * @return
- *   The pointer to the duplicated string, or NULL on error (errno is set).
- */
-#define ec_strdup(s) ({                                                        \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.malloc == NULL)                           \
-               ret_ = strdup(s);                                       \
-       else                                                            \
-               ret_ = __ec_strdup(s, __FILE__, __LINE__);              \
-       ret_;                                                           \
-       })
-
-/**
- * Duplicate at most n bytes of a string.
- *
- * This function is similar to ec_strdup(), except that it copies at
- * most n bytes.  If s is longer than n, only n bytes are copied, and a
- * terminating null byte ('\0') is added.
- *
- * @param s
- *   The string to be duplicated.
- * @param n
- *   The maximum length of the new string.
- * @return
- *   The pointer to the duplicated string, or NULL on error (errno is set).
- */
-#define ec_strndup(s, n) ({                                            \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.malloc == NULL)                           \
-               ret_ = strndup(s, n);                                   \
-       else                                                            \
-               ret_ = __ec_strndup(s, n, __FILE__, __LINE__);          \
-       ret_;                                                           \
-       })
-
-/* internal */
-void *__ec_malloc(size_t size, const char *file, unsigned int line);
-void __ec_free(void *ptr, const char *file, unsigned int line);
-void *__ec_calloc(size_t nmemb, size_t size, const char *file,
-       unsigned int line);
-void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line);
-char *__ec_strdup(const char *s, const char *file, unsigned int line);
-char *__ec_strndup(const char *s, size_t n, const char *file,
-       unsigned int line);
-
-
-#endif
diff --git a/lib/ecoli_murmurhash.c b/lib/ecoli_murmurhash.c
deleted file mode 100644 (file)
index 7aafece..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdint.h>
-
-#include <ecoli_murmurhash.h>
-
-uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed)
-{
-       const uint8_t *data = (const uint8_t *)key;
-       const uint8_t *tail;
-       const int nblocks = len / 4;
-       uint32_t h1 = seed;
-       uint32_t k1;
-       const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4);
-       int i;
-
-       for (i = -nblocks; i; i++) {
-               k1 = blocks[i];
-
-               h1 = ec_murmurhash3_add32(h1, k1);
-               h1 = ec_murmurhash3_mix32(h1);
-       }
-
-       tail = (const uint8_t *)(data + nblocks * 4);
-       k1 = 0;
-
-       switch(len & 3) {
-       case 3: k1 ^= tail[2] << 16;
-       case 2: k1 ^= tail[1] << 8;
-       case 1: k1 ^= tail[0];
-               h1 = ec_murmurhash3_add32(h1, k1);
-       };
-
-       /* finalization */
-       h1 ^= len;
-       h1 = ec_murmurhash3_fmix32(h1);
-       return h1;
-}
diff --git a/lib/ecoli_murmurhash.h b/lib/ecoli_murmurhash.h
deleted file mode 100644 (file)
index 6b76d34..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * MurmurHash3 is a hash implementation that was written by Austin Appleby, and
- * is placed in the public domain. The author hereby disclaims copyright to this
- * source code.
- */
-
-#ifndef ECOLI_MURMURHASH_H_
-#define ECOLI_MURMURHASH_H_
-
-#include <stdint.h>
-
-/** Hash rotation */
-static inline uint32_t ec_murmurhash_rotl32(uint32_t x, int8_t r)
-{
-       return (x << r) | (x >> (32 - r));
-}
-
-/** Add 32-bit to the hash */
-static inline uint32_t ec_murmurhash3_add32(uint32_t h, uint32_t data)
-{
-       data *= 0xcc9e2d51;
-       data = ec_murmurhash_rotl32(data, 15);
-       data *= 0x1b873593;
-       h ^= data;
-       return h;
-}
-
-/** Intermediate mix */
-static inline uint32_t ec_murmurhash3_mix32(uint32_t h)
-{
-       h = ec_murmurhash_rotl32(h,13);
-       h = h * 5 +0xe6546b64;
-       return h;
-}
-
-/** Final mix: force all bits of a hash block to avalanche */
-static inline uint32_t ec_murmurhash3_fmix32(uint32_t h)
-{
-       h ^= h >> 16;
-       h *= 0x85ebca6b;
-       h ^= h >> 13;
-       h *= 0xc2b2ae35;
-       h ^= h >> 16;
-
-       return h;
-}
-
-/**
- * Calculate a 32-bit murmurhash3
- *
- * @param key
- *   The key (the unaligned variable-length array of bytes).
- * @param len
- *   The length of the key, counting by bytes.
- * @param seed
- *   Can be any 4-byte value initialization value.
- * @return
- *   A 32-bit hash.
- */
-uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed);
-
-#endif /* ECOLI_MURMURHASH_H_ */
diff --git a/lib/ecoli_node.c b/lib/ecoli_node.c
deleted file mode 100644 (file)
index c02a8de..0000000
+++ /dev/null
@@ -1,607 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_log.h>
-#include <ecoli_config.h>
-#include <ecoli_test.h>
-#include <ecoli_node.h>
-
-#include <ecoli_node_str.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_int.h>
-
-EC_LOG_TYPE_REGISTER(node);
-
-static struct ec_node_type_list node_type_list =
-       TAILQ_HEAD_INITIALIZER(node_type_list);
-
-const struct ec_node_type *
-ec_node_type_lookup(const char *name)
-{
-       struct ec_node_type *type;
-
-       TAILQ_FOREACH(type, &node_type_list, next) {
-               if (!strcmp(name, type->name))
-                       return type;
-       }
-
-       errno = ENOENT;
-       return NULL;
-}
-
-int ec_node_type_register(struct ec_node_type *type)
-{
-       EC_CHECK_ARG(type->size >= sizeof(struct ec_node), -1, EINVAL);
-
-       if (ec_node_type_lookup(type->name) != NULL) {
-               errno = EEXIST;
-               return -1;
-       }
-
-       TAILQ_INSERT_TAIL(&node_type_list, type, next);
-
-       return 0;
-}
-
-void ec_node_type_dump(FILE *out)
-{
-       struct ec_node_type *type;
-
-       TAILQ_FOREACH(type, &node_type_list, next)
-               fprintf(out, "%s\n", type->name);
-}
-
-struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id)
-{
-       struct ec_node *node = NULL;
-
-       EC_LOG(EC_LOG_DEBUG, "create node type=%s id=%s\n",
-               type->name, id);
-       if (id == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       node = ec_calloc(1, type->size);
-       if (node == NULL)
-               goto fail;
-
-       node->type = type;
-       node->refcnt = 1;
-
-       node->id = ec_strdup(id);
-       if (node->id == NULL)
-               goto fail;
-
-       if (ec_asprintf(&node->desc, "<%s>", type->name) < 0)
-               goto fail;
-
-       node->attrs = ec_keyval();
-       if (node->attrs == NULL)
-               goto fail;
-
-       if (type->init_priv != NULL) {
-               if (type->init_priv(node) < 0)
-                       goto fail;
-       }
-
-       return node;
-
- fail:
-       if (node != NULL) {
-               ec_keyval_free(node->attrs);
-               ec_free(node->desc);
-               ec_free(node->id);
-       }
-       ec_free(node);
-
-       return NULL;
-}
-
-const struct ec_config_schema *
-ec_node_type_schema(const struct ec_node_type *type)
-{
-       return type->schema;
-}
-
-const char *
-ec_node_type_name(const struct ec_node_type *type)
-{
-       return type->name;
-}
-
-struct ec_node *ec_node(const char *typename, const char *id)
-{
-       const struct ec_node_type *type;
-
-       type = ec_node_type_lookup(typename);
-       if (type == NULL) {
-               EC_LOG(EC_LOG_ERR, "type=%s does not exist\n",
-                       typename);
-               return NULL;
-       }
-
-       return ec_node_from_type(type, id);
-}
-
-static void count_references(struct ec_node *node, unsigned int refs)
-{
-       struct ec_node *child;
-       size_t i, n;
-       int ret;
-
-       if (node->free.state == EC_NODE_FREE_STATE_TRAVERSED) {
-               node->free.refcnt += refs;
-               return;
-       }
-       node->free.refcnt = refs;
-       node->free.state = EC_NODE_FREE_STATE_TRAVERSED;
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               count_references(child, refs);
-       }
-}
-
-static void mark_freeable(struct ec_node *node, enum ec_node_free_state mark)
-{
-       struct ec_node *child;
-       unsigned int refs;
-       size_t i, n;
-       int ret;
-
-       if (mark == node->free.state)
-               return;
-
-       if (node->refcnt > node->free.refcnt)
-               mark = EC_NODE_FREE_STATE_NOT_FREEABLE;
-       assert(node->refcnt >= node->free.refcnt);
-       node->free.state = mark;
-
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               mark_freeable(child, mark);
-       }
-}
-
-static void reset_mark(struct ec_node *node)
-{
-       struct ec_node *child;
-       unsigned int refs;
-       size_t i, n;
-       int ret;
-
-       if (node->free.state == EC_NODE_FREE_STATE_NONE)
-               return;
-
-       node->free.state = EC_NODE_FREE_STATE_NONE;
-       node->free.refcnt = 0;
-
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               reset_mark(child);
-       }
-}
-
-/* free a node, taking care of loops in the node graph */
-void ec_node_free(struct ec_node *node)
-{
-       size_t n;
-
-       if (node == NULL)
-               return;
-
-       assert(node->refcnt > 0);
-
-       if (node->free.state == EC_NODE_FREE_STATE_NONE &&
-                       node->refcnt != 1) {
-
-               /* Traverse the node tree starting from this node, and for each
-                * node, count the number of reachable references. Then, all
-                * nodes whose reachable references == total reference are
-                * marked as freeable, and other are marked as unfreeable. Any
-                * node reachable from an unfreeable node is also marked as
-                * unfreeable. */
-               if (node->free.state == EC_NODE_FREE_STATE_NONE) {
-                       count_references(node, 1);
-                       mark_freeable(node, EC_NODE_FREE_STATE_FREEABLE);
-               }
-       }
-
-       if (node->free.state == EC_NODE_FREE_STATE_NOT_FREEABLE) {
-               node->refcnt--;
-               reset_mark(node);
-               return;
-       }
-
-       if (node->free.state != EC_NODE_FREE_STATE_FREEING) {
-               node->free.state = EC_NODE_FREE_STATE_FREEING;
-
-               /* children will be freed by config_free() and free_priv() */
-               ec_config_free(node->config);
-               node->config = NULL;
-               n = ec_node_get_children_count(node);
-               assert(n == 0 || node->type->free_priv != NULL);
-               if (node->type->free_priv != NULL)
-                       node->type->free_priv(node);
-               ec_free(node->id);
-               ec_free(node->desc);
-               ec_keyval_free(node->attrs);
-       }
-
-       node->refcnt--;
-       if (node->refcnt != 0)
-               return;
-
-       node->free.state = EC_NODE_FREE_STATE_NONE;
-       node->free.refcnt = 0;
-
-       ec_free(node);
-}
-
-struct ec_node *ec_node_clone(struct ec_node *node)
-{
-       if (node != NULL)
-               node->refcnt++;
-       return node;
-}
-
-size_t ec_node_get_children_count(const struct ec_node *node)
-{
-       if (node->type->get_children_count == NULL)
-               return 0;
-       return node->type->get_children_count(node);
-}
-
-int
-ec_node_get_child(const struct ec_node *node, size_t i,
-       struct ec_node **child, unsigned int *refs)
-{
-       *child = NULL;
-       *refs = 0;
-       if (node->type->get_child == NULL)
-               return -1;
-       return node->type->get_child(node, i, child, refs);
-}
-
-int
-ec_node_set_config(struct ec_node *node, struct ec_config *config)
-{
-       if (node->type->schema == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-       if (ec_config_validate(config, node->type->schema) < 0)
-               goto fail;
-       if (node->type->set_config != NULL) {
-               if (node->type->set_config(node, config) < 0)
-                       goto fail;
-       }
-
-       ec_config_free(node->config);
-       node->config = config;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       return -1;
-}
-
-const struct ec_config *ec_node_get_config(struct ec_node *node)
-{
-       return node->config;
-}
-
-struct ec_node *ec_node_find(struct ec_node *node, const char *id)
-{
-       struct ec_node *child, *retnode;
-       const char *node_id = ec_node_id(node);
-       unsigned int refs;
-       size_t i, n;
-       int ret;
-
-       if (id != NULL && node_id != NULL && !strcmp(node_id, id))
-               return node;
-
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               retnode = ec_node_find(child, id);
-               if (retnode != NULL)
-                       return retnode;
-       }
-
-       return NULL;
-}
-
-const struct ec_node_type *ec_node_type(const struct ec_node *node)
-{
-       return node->type;
-}
-
-struct ec_keyval *ec_node_attrs(const struct ec_node *node)
-{
-       return node->attrs;
-}
-
-const char *ec_node_id(const struct ec_node *node)
-{
-       return node->id;
-}
-
-static void __ec_node_dump(FILE *out,
-       const struct ec_node *node, size_t indent, struct ec_keyval *dict)
-{
-       const char *id, *typename;
-       struct ec_node *child;
-       unsigned int refs;
-       char buf[32];
-       size_t i, n;
-       int ret;
-
-       id = ec_node_id(node);
-       typename = node->type->name;
-
-       snprintf(buf, sizeof(buf), "%p", node);
-       if (ec_keyval_has_key(dict, buf)) {
-               fprintf(out, "%*s" "type=%s id=%s %p... (loop)\n",
-                       (int)indent * 4, "", typename, id, node);
-               return;
-       }
-
-       ec_keyval_set(dict, buf, NULL, NULL);
-       fprintf(out, "%*s" "type=%s id=%s %p refs=%u free_state=%d free_refs=%d\n",
-               (int)indent * 4, "", typename, id, node, node->refcnt,
-               node->free.state, node->free.refcnt);
-
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               __ec_node_dump(out, child, indent + 1, dict);
-       }
-}
-
-/* XXX this is too much debug-oriented, we should have a parameter or 2 funcs */
-void ec_node_dump(FILE *out, const struct ec_node *node)
-{
-       struct ec_keyval *dict = NULL;
-
-       fprintf(out, "------------------- node dump:\n");
-
-       if (node == NULL) {
-               fprintf(out, "node is NULL\n");
-               return;
-       }
-
-       dict = ec_keyval();
-       if (dict == NULL)
-               goto fail;
-
-       __ec_node_dump(out, node, 0, dict);
-
-       ec_keyval_free(dict);
-       return;
-
-fail:
-       ec_keyval_free(dict);
-       EC_LOG(EC_LOG_ERR, "failed to dump node\n");
-}
-
-const char *ec_node_desc(const struct ec_node *node)
-{
-       if (node->type->desc != NULL)
-               return node->type->desc(node);
-
-       return node->desc;
-}
-
-int ec_node_check_type(const struct ec_node *node,
-               const struct ec_node_type *type)
-{
-       if (strcmp(node->type->name, type->name)) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       return 0;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_testcase(void)
-{
-       struct ec_node *node = NULL, *expr = NULL;
-       struct ec_node *expr2 = NULL, *val = NULL, *op = NULL, *seq = NULL;
-       const struct ec_node_type *type;
-       struct ec_node *child;
-       unsigned int refs;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-       int testres = 0;
-       int ret;
-
-       node = EC_NODE_SEQ(EC_NO_ID,
-                       ec_node_str("id_x", "x"),
-                       ec_node_str("id_y", "y"));
-       if (node == NULL)
-               goto fail;
-
-       ec_node_clone(node);
-       ec_node_free(node);
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_node_dump(f, node);
-       ec_node_type_dump(f);
-       ec_node_dump(f, NULL);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "type=seq id=no-id"), "bad dump\n");
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "type=str id=id_x") &&
-               strstr(strstr(buf, "type=str id=id_x") + 1,
-                       "type=str id=id_y"),
-               "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_node_type(node)->name, "seq") &&
-               !strcmp(ec_node_id(node), EC_NO_ID) &&
-               !strcmp(ec_node_desc(node), "<seq>"),
-               "bad child 0");
-
-       testres |= EC_TEST_CHECK(
-               ec_node_get_children_count(node) == 2,
-               "bad children count\n");
-       ret = ec_node_get_child(node, 0, &child, &refs);
-       testres |= EC_TEST_CHECK(ret == 0 &&
-               child != NULL &&
-               !strcmp(ec_node_type(child)->name, "str") &&
-               !strcmp(ec_node_id(child), "id_x"),
-               "bad child 0");
-       ret = ec_node_get_child(node, 1, &child, &refs);
-       testres |= EC_TEST_CHECK(ret == 0 &&
-               child != NULL &&
-               !strcmp(ec_node_type(child)->name, "str") &&
-               !strcmp(ec_node_id(child), "id_y"),
-               "bad child 1");
-       ret = ec_node_get_child(node, 2, &child, &refs);
-       testres |= EC_TEST_CHECK(ret != 0,
-               "ret should be != 0");
-       testres |= EC_TEST_CHECK(child == NULL,
-               "child 2 should be NULL");
-
-       child = ec_node_find(node, "id_x");
-       testres |= EC_TEST_CHECK(child != NULL &&
-               !strcmp(ec_node_type(child)->name, "str") &&
-               !strcmp(ec_node_id(child), "id_x") &&
-               !strcmp(ec_node_desc(child), "x"),
-               "bad child id_x");
-       child = ec_node_find(node, "id_dezdex");
-       testres |= EC_TEST_CHECK(child == NULL,
-               "child with wrong id should be NULL");
-
-       ret = ec_keyval_set(ec_node_attrs(node), "key", "val", NULL);
-       testres |= EC_TEST_CHECK(ret == 0,
-               "cannot set node attribute\n");
-
-       type = ec_node_type_lookup("seq");
-       testres |= EC_TEST_CHECK(type != NULL &&
-               ec_node_check_type(node, type) == 0,
-               "cannot get seq node type");
-       type = ec_node_type_lookup("str");
-       testres |= EC_TEST_CHECK(type != NULL &&
-               ec_node_check_type(node, type) < 0,
-               "node type should not be str");
-
-       ec_node_free(node);
-       node = NULL;
-
-       node = ec_node("deznuindez", EC_NO_ID);
-       testres |= EC_TEST_CHECK(node == NULL,
-                       "should not be able to create node\n");
-
-       /* test loop */
-       expr = ec_node("or", EC_NO_ID);
-       val = ec_node_int(EC_NO_ID, 0, 10, 0);
-       op = ec_node_str(EC_NO_ID, "!");
-       seq = EC_NODE_SEQ(EC_NO_ID,
-                       op,
-                       ec_node_clone(expr));
-       op = NULL;
-       if (expr == NULL || val == NULL || seq == NULL)
-               goto fail;
-       if (ec_node_or_add(expr, ec_node_clone(seq)) < 0)
-               goto fail;
-       ec_node_free(seq);
-       seq = NULL;
-       if (ec_node_or_add(expr, ec_node_clone(val)) < 0)
-               goto fail;
-       ec_node_free(val);
-       val = NULL;
-
-       testres |= EC_TEST_CHECK_PARSE(expr, 1, "1");
-       testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1");
-       testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!");
-
-       ec_node_free(expr);
-       expr = NULL;
-
-       /* same loop test, but keep some refs (released later) */
-       expr = ec_node("or", EC_NO_ID);
-       ec_node_clone(expr);
-       expr2 = expr;
-       val = ec_node_int(EC_NO_ID, 0, 10, 0);
-       op = ec_node_str(EC_NO_ID, "!");
-       seq = EC_NODE_SEQ(EC_NO_ID,
-                       op,
-                       ec_node_clone(expr));
-       op = NULL;
-       if (expr == NULL || val == NULL || seq == NULL)
-               goto fail;
-       if (ec_node_or_add(expr, ec_node_clone(seq)) < 0)
-               goto fail;
-       ec_node_free(seq);
-       seq = NULL;
-       if (ec_node_or_add(expr, ec_node_clone(val)) < 0)
-               goto fail;
-
-       testres |= EC_TEST_CHECK_PARSE(expr, 1, "1");
-       testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1");
-       testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!");
-
-       ec_node_free(expr2);
-       expr2 = NULL;
-       ec_node_free(val);
-       val = NULL;
-       ec_node_free(expr);
-       expr = NULL;
-
-       return testres;
-
-fail:
-       ec_node_free(expr);
-       ec_node_free(expr2);
-       ec_node_free(val);
-       ec_node_free(seq);
-       ec_node_free(node);
-       if (f != NULL)
-               fclose(f);
-       free(buf);
-
-       assert(errno != 0);
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_test = {
-       .name = "node",
-       .test = ec_node_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_test);
diff --git a/lib/ecoli_node.h b/lib/ecoli_node.h
deleted file mode 100644 (file)
index 45f3e74..0000000
+++ /dev/null
@@ -1,206 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Interface to manage the ecoli nodes.
- *
- * A node is a main structure of the ecoli library, used to define how
- * to match and complete the input tokens. A node is a generic object
- * that implements:
- * - a parse(node, input) method: check if an input matches
- * - a complete(node, input) method: return possible completions for
- *   a given input
- * - some other methods to initialize, free, ...
- *
- * One basic example is the string node (ec_node_str). A node
- * ec_node_str("foo") will match any token list starting with "foo",
- * for example:
- * - ["foo"]
- * - ["foo", "bar", ...]
- * But will not match:
- * - []
- * - ["bar", ...]
- *
- * A node ec_node_str("foo") will complete with "foo" if the input
- * contains one token, with the same beginning than "foo":
- * - [""]
- * - ["f"]
- * - ["fo"]
- * - ["foo"]
- * But it will not complete:
- * - []
- * - ["bar"]
- * - ["f", ""]
- * - ["", "f"]
- *
- * A node can have child nodes. For instance, a sequence node
- * ec_node_seq(ec_node_str("foo"), ec_node_str("bar")) will match
- * ["foo", "bar"].
- */
-
-#ifndef ECOLI_NODE_
-#define ECOLI_NODE_
-
-#include <sys/queue.h>
-#include <sys/types.h>
-#include <stdio.h>
-
-#define EC_NO_ID "no-id"
-
-#define EC_NODE_ENDLIST ((void *)1)
-
-struct ec_node;
-struct ec_parse;
-struct ec_comp;
-struct ec_strvec;
-struct ec_keyval;
-struct ec_config;
-struct ec_config_schema;
-
-#define EC_NODE_TYPE_REGISTER(t)                                       \
-       static void ec_node_init_##t(void);                             \
-       static void __attribute__((constructor, used))                  \
-       ec_node_init_##t(void)                                          \
-       {                                                               \
-               if (ec_node_type_register(&t) < 0)                      \
-                       fprintf(stderr,                                 \
-                               "cannot register node type %s\n",       \
-                               t.name);                                \
-       }
-
-TAILQ_HEAD(ec_node_type_list, ec_node_type);
-
-typedef int (*ec_node_set_config_t)(struct ec_node *node,
-                               const struct ec_config *config);
-typedef int (*ec_node_parse_t)(const struct ec_node *node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec);
-typedef int (*ec_node_complete_t)(const struct ec_node *node,
-                               struct ec_comp *comp_state,
-                               const struct ec_strvec *strvec);
-typedef const char * (*ec_node_desc_t)(const struct ec_node *);
-typedef int (*ec_node_init_priv_t)(struct ec_node *);
-typedef void (*ec_node_free_priv_t)(struct ec_node *);
-typedef size_t (*ec_node_get_children_count_t)(const struct ec_node *);
-typedef int (*ec_node_get_child_t)(const struct ec_node *,
-       size_t i, struct ec_node **child, unsigned int *refs);
-
-/**
- * A structure describing a node type.
- */
-struct ec_node_type {
-       TAILQ_ENTRY(ec_node_type) next;  /**< Next in list. */
-       const char *name;                /**< Node type name. */
-       /** Configuration schema array, must be terminated by a sentinel
-        *  (.type = EC_CONFIG_TYPE_NONE). */
-       const struct ec_config_schema *schema;
-       ec_node_set_config_t set_config; /* validate/ack a config change */
-       ec_node_parse_t parse;
-       ec_node_complete_t complete;
-       ec_node_desc_t desc;
-       size_t size;
-       ec_node_init_priv_t init_priv;
-       ec_node_free_priv_t free_priv;
-       ec_node_get_children_count_t get_children_count;
-       ec_node_get_child_t get_child;
-};
-
-/**
- * Register a node type.
- *
- * @param type
- *   A pointer to a ec_test structure describing the test
- *   to be registered.
- * @return
- *   0 on success, negative value on error.
- */
-int ec_node_type_register(struct ec_node_type *type);
-
-/**
- * Lookup node type by name
- *
- * @param name
- *   The name of the node type to search.
- * @return
- *   The node type if found, or NULL on error.
- */
-const struct ec_node_type *ec_node_type_lookup(const char *name);
-
-/**
- * Dump registered log types
- */
-void ec_node_type_dump(FILE *out);
-
-/**
- * Get the config schema of a node type.
- */
-const struct ec_config_schema *
-ec_node_type_schema(const struct ec_node_type *type);
-
-/**
- * Get the name of a node type.
- */
-const char *
-ec_node_type_name(const struct ec_node_type *type);
-
-enum ec_node_free_state {
-       EC_NODE_FREE_STATE_NONE,
-       EC_NODE_FREE_STATE_TRAVERSED,
-       EC_NODE_FREE_STATE_FREEABLE,
-       EC_NODE_FREE_STATE_NOT_FREEABLE,
-       EC_NODE_FREE_STATE_FREEING,
-};
-
-struct ec_node {
-       const struct ec_node_type *type;
-       struct ec_config *config;    /**< Generic configuration. */
-       char *id;
-       char *desc;
-       struct ec_keyval *attrs;
-       unsigned int refcnt;
-       struct {
-               enum ec_node_free_state state; /**< State of loop detection */
-               unsigned int refcnt;    /**< Number of reachable references
-                                        *   starting from node beeing freed */
-       } free; /**< Freeing state: used for loop detection */
-};
-
-/* create a new node when the type is known, typically called from the node
- * code */
-struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id);
-
-/* create a new node */
-struct ec_node *ec_node(const char *typename, const char *id);
-
-struct ec_node *ec_node_clone(struct ec_node *node);
-void ec_node_free(struct ec_node *node);
-
-/* set configuration of a node
- * after a call to this function, the config is
- * owned by the node and must not be used by the caller
- * on error, the config is freed. */
-int ec_node_set_config(struct ec_node *node, struct ec_config *config);
-
-/* get the current node configuration. Return NULL if no configuration. */
-const struct ec_config *ec_node_get_config(struct ec_node *node);
-
-size_t ec_node_get_children_count(const struct ec_node *node);
-int
-ec_node_get_child(const struct ec_node *node, size_t i,
-       struct ec_node **child, unsigned int *refs);
-
-/* XXX add more accessors */
-const struct ec_node_type *ec_node_type(const struct ec_node *node);
-struct ec_keyval *ec_node_attrs(const struct ec_node *node);
-const char *ec_node_id(const struct ec_node *node);
-const char *ec_node_desc(const struct ec_node *node);
-
-void ec_node_dump(FILE *out, const struct ec_node *node);
-struct ec_node *ec_node_find(struct ec_node *node, const char *id);
-
-/* check the type of a node */
-int ec_node_check_type(const struct ec_node *node,
-               const struct ec_node_type *type);
-
-#endif
diff --git a/lib/ecoli_node_any.c b/lib/ecoli_node_any.c
deleted file mode 100644 (file)
index 197a2c5..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_any.h>
-
-EC_LOG_TYPE_REGISTER(node_any);
-
-struct ec_node_any {
-       struct ec_node gen;
-};
-
-static int ec_node_any_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)state;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-static struct ec_node_type ec_node_any_type = {
-       .name = "any",
-       .parse = ec_node_any_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_any),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_any_type);
-
-/* LCOV_EXCL_START */
-static int ec_node_any_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("any", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-       ec_node_free(node);
-
-       /* never completes */
-       node = ec_node("any", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_any_test = {
-       .name = "node_any",
-       .test = ec_node_any_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_any_test);
diff --git a/lib/ecoli_node_any.h b/lib/ecoli_node_any.h
deleted file mode 100644 (file)
index ee638aa..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * This node always matches 1 string in the vector
- */
-
-#ifndef ECOLI_NODE_ANY_
-#define ECOLI_NODE_ANY_
-
-/* no specific API for this node */
-
-#endif
diff --git a/lib/ecoli_node_cmd.c b/lib/ecoli_node_cmd.c
deleted file mode 100644 (file)
index c61b759..0000000
+++ /dev/null
@@ -1,682 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/queue.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <limits.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_helper.h>
-#include <ecoli_node_expr.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_subset.h>
-#include <ecoli_node_int.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_option.h>
-#include <ecoli_node_re.h>
-#include <ecoli_node_re_lex.h>
-#include <ecoli_node_cmd.h>
-
-EC_LOG_TYPE_REGISTER(node_cmd);
-
-struct ec_node_cmd {
-       struct ec_node gen;
-       char *cmd_str;           /* the command string. */
-       struct ec_node *cmd;     /* the command node. */
-       struct ec_node *parser;  /* the expression parser. */
-       struct ec_node *expr;    /* the expression parser without lexer. */
-       struct ec_node **table;  /* table of node referenced in command. */
-       unsigned int len;        /* len of the table. */
-};
-
-/* passed as user context to expression parser */
-struct ec_node_cmd_ctx {
-       struct ec_node **table;
-       unsigned int len;
-};
-
-static int
-ec_node_cmd_eval_var(void **result, void *userctx,
-       const struct ec_parse *var)
-{
-       const struct ec_strvec *vec;
-       struct ec_node_cmd_ctx *ctx = userctx;
-       struct ec_node *eval = NULL;
-       const char *str, *id;
-       unsigned int i;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(var);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-       str = ec_strvec_val(vec, 0);
-
-       for (i = 0; i < ctx->len; i++) {
-               id = ec_node_id(ctx->table[i]);
-               if (id == NULL)
-                       continue;
-               if (strcmp(str, id))
-                       continue;
-               /* if id matches, use a node provided by the user... */
-               eval = ec_node_clone(ctx->table[i]);
-               if (eval == NULL)
-                       return -1;
-               break;
-       }
-
-       /* ...or create a string node */
-       if (eval == NULL) {
-               eval = ec_node_str(EC_NO_ID, str);
-               if (eval == NULL)
-                       return -1;
-       }
-
-       *result = eval;
-
-       return 0;
-}
-
-static int
-ec_node_cmd_eval_pre_op(void **result, void *userctx, void *operand,
-       const struct ec_parse *operator)
-{
-       (void)result;
-       (void)userctx;
-       (void)operand;
-       (void)operator;
-
-       errno = EINVAL;
-       return -1;
-}
-
-static int
-ec_node_cmd_eval_post_op(void **result, void *userctx, void *operand,
-       const struct ec_parse *operator)
-{
-       const struct ec_strvec *vec;
-       struct ec_node *in = operand;;
-       struct ec_node *out = NULL;;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "*")) {
-               out = ec_node_many(EC_NO_ID,
-                               ec_node_clone(in), 0, 0);
-               if (out == NULL)
-                       return -1;
-               ec_node_free(in);
-               *result = out;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       return 0;
-}
-
-static int
-ec_node_cmd_eval_bin_op(void **result, void *userctx, void *operand1,
-       const struct ec_parse *operator, void *operand2)
-
-{
-       const struct ec_strvec *vec;
-       struct ec_node *out = NULL;
-       struct ec_node *in1 = operand1;
-       struct ec_node *in2 = operand2;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) > 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (ec_strvec_len(vec) == 0) {
-               if (!strcmp(in1->type->name, "seq")) {
-                       if (ec_node_seq_add(in1, ec_node_clone(in2)) < 0)
-                               return -1;
-                       ec_node_free(in2);
-                       *result = in1;
-               } else {
-                       out = EC_NODE_SEQ(EC_NO_ID, ec_node_clone(in1),
-                                       ec_node_clone(in2));
-                       if (out == NULL)
-                               return -1;
-                       ec_node_free(in1);
-                       ec_node_free(in2);
-                       *result = out;
-               }
-       } else if (!strcmp(ec_strvec_val(vec, 0), "|")) {
-               if (!strcmp(in2->type->name, "or")) {
-                       if (ec_node_or_add(in2, ec_node_clone(in1)) < 0)
-                               return -1;
-                       ec_node_free(in1);
-                       *result = in2;
-               } else if (!strcmp(in1->type->name, "or")) {
-                       if (ec_node_or_add(in1, ec_node_clone(in2)) < 0)
-                               return -1;
-                       ec_node_free(in2);
-                       *result = in1;
-               } else {
-                       out = EC_NODE_OR(EC_NO_ID, ec_node_clone(in1),
-                                       ec_node_clone(in2));
-                       if (out == NULL)
-                               return -1;
-                       ec_node_free(in1);
-                       ec_node_free(in2);
-                       *result = out;
-               }
-       } else if (!strcmp(ec_strvec_val(vec, 0), ",")) {
-               if (!strcmp(in2->type->name, "subset")) {
-                       if (ec_node_subset_add(in2, ec_node_clone(in1)) < 0)
-                               return -1;
-                       ec_node_free(in1);
-                       *result = in2;
-               } else if (!strcmp(in1->type->name, "subset")) {
-                       if (ec_node_subset_add(in1, ec_node_clone(in2)) < 0)
-                               return -1;
-                       ec_node_free(in2);
-                       *result = in1;
-               } else {
-                       out = EC_NODE_SUBSET(EC_NO_ID, ec_node_clone(in1),
-                                       ec_node_clone(in2));
-                       if (out == NULL)
-                               return -1;
-                       ec_node_free(in1);
-                       ec_node_free(in2);
-                       *result = out;
-               }
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       return 0;
-}
-
-static int
-ec_node_cmd_eval_parenthesis(void **result, void *userctx,
-       const struct ec_parse *open_paren,
-       const struct ec_parse *close_paren,
-       void *value)
-{
-       const struct ec_strvec *vec;
-       struct ec_node *in = value;;
-       struct ec_node *out = NULL;;
-
-       (void)userctx;
-       (void)close_paren;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(open_paren);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "[")) {
-               out = ec_node_option(EC_NO_ID, ec_node_clone(in));
-               if (out == NULL)
-                       return -1;
-               ec_node_free(in);
-       } else if (!strcmp(ec_strvec_val(vec, 0), "(")) {
-               out = in;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       *result = out;
-
-       return 0;
-}
-
-static void
-ec_node_cmd_eval_free(void *result, void *userctx)
-{
-       (void)userctx;
-       ec_free(result);
-}
-
-static const struct ec_node_expr_eval_ops expr_ops = {
-       .eval_var = ec_node_cmd_eval_var,
-       .eval_pre_op = ec_node_cmd_eval_pre_op,
-       .eval_post_op = ec_node_cmd_eval_post_op,
-       .eval_bin_op = ec_node_cmd_eval_bin_op,
-       .eval_parenthesis = ec_node_cmd_eval_parenthesis,
-       .eval_free = ec_node_cmd_eval_free,
-};
-
-static struct ec_node *
-ec_node_cmd_build_expr(void)
-{
-       struct ec_node *expr = NULL;
-       int ret;
-
-       /* build the expression parser */
-       expr = ec_node("expr", "expr");
-       if (expr == NULL)
-               goto fail;
-       ret = ec_node_expr_set_val_node(expr, ec_node_re(EC_NO_ID,
-                                       "[a-zA-Z0-9]+"));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, ","));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, "|"));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_bin_op(expr, ec_node("empty", EC_NO_ID));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "+"));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "*"));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "["),
-               ec_node_str(EC_NO_ID, "]"));
-       if (ret < 0)
-               goto fail;
-       ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "("),
-               ec_node_str(EC_NO_ID, ")"));
-       if (ret < 0)
-               goto fail;
-
-       return expr;
-
-fail:
-       ec_node_free(expr);
-       return NULL;
-}
-
-static struct ec_node *
-ec_node_cmd_build_parser(struct ec_node *expr)
-{
-       struct ec_node *lex = NULL;
-       int ret;
-
-       /* prepend a lexer to the expression node */
-       lex = ec_node_re_lex(EC_NO_ID, ec_node_clone(expr));
-       if (lex == NULL)
-               goto fail;
-
-       ret = ec_node_re_lex_add(lex, "[a-zA-Z0-9]+", 1);
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_re_lex_add(lex, "[*|,()]", 1);
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_re_lex_add(lex, "\\[", 1);
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_re_lex_add(lex, "\\]", 1);
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_re_lex_add(lex, "[         ]+", 0);
-       if (ret < 0)
-               goto fail;
-
-       return lex;
-
-fail:
-       ec_node_free(lex);
-
-       return NULL;
-}
-
-static struct ec_node *
-ec_node_cmd_build(struct ec_node_cmd *node, const char *cmd_str,
-       struct ec_node **table, size_t len)
-{
-       struct ec_node_cmd_ctx ctx = { table, len };
-       struct ec_parse *p = NULL;
-       void *result;
-       int ret;
-
-       /* parse the command expression */
-       p = ec_node_parse(node->parser, cmd_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;
-       }
-
-       ret = ec_node_expr_eval(&result, node->expr,
-                               ec_parse_get_first_child(p),
-                               &expr_ops, &ctx);
-       if (ret < 0)
-               goto fail;
-
-       ec_parse_free(p);
-       return result;
-
-fail:
-       ec_parse_free(p);
-       return NULL;
-}
-
-static int
-ec_node_cmd_parse(const struct ec_node *gen_node, struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-
-       return ec_node_parse_child(node->cmd, state, strvec);
-}
-
-static int
-ec_node_cmd_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-
-       return ec_node_complete_child(node->cmd, comp, strvec);
-}
-
-static void ec_node_cmd_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-       size_t i;
-
-       ec_free(node->cmd_str);
-       node->cmd_str = NULL;
-       ec_node_free(node->expr);
-       node->expr = NULL;
-       ec_node_free(node->parser);
-       node->parser = NULL;
-       ec_node_free(node->cmd);
-       node->cmd = NULL;
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = NULL;
-       node->len = 0;
-}
-
-static const struct ec_config_schema ec_node_cmd_subschema[] = {
-       {
-               .desc = "A child node whose id is referenced in the expression.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema ec_node_cmd_schema[] = {
-       {
-               .key = "expr",
-               .desc = "The expression to match. Supported operators "
-               "are or '|', list ',', many '+', many-or-zero '*', "
-               "option '[]', group '()'. An identifier (alphanumeric) can "
-               "reference a node whose node_id matches. Else it is "
-               "interpreted as ec_node_str() matching this string. "
-               "Example: command [option] (subset1, subset2) x|y",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .key = "children",
-               .desc = "The list of children nodes.",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = ec_node_cmd_subschema,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_cmd_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-       const struct ec_config *expr = NULL;
-       struct ec_node *cmd = NULL;
-       struct ec_node **table = NULL;
-       char *cmd_str = NULL;
-       size_t len = 0, i;
-
-       /* retrieve config locally */
-       expr = ec_config_dict_get(config, "expr");
-       if (expr == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       table = ec_node_config_node_list_to_table(
-               ec_config_dict_get(config, "children"), &len);
-       if (table == NULL)
-               goto fail;
-
-       cmd_str = ec_strdup(expr->string);
-       if (cmd_str == NULL)
-               goto fail;
-
-       /* parse expression to build the cmd child node */
-       cmd = ec_node_cmd_build(node, cmd_str, table, len);
-       if (cmd == NULL)
-               goto fail;
-
-       /* ok, store the config */
-       ec_node_free(node->cmd);
-       node->cmd = cmd;
-       ec_free(node->cmd_str);
-       node->cmd_str = cmd_str;
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = table;
-       node->len = len;
-
-       return 0;
-
-fail:
-       for (i = 0; i < len; i++)
-               ec_node_free(table[i]);
-       ec_free(table);
-       ec_free(cmd_str);
-       ec_node_free(cmd);
-       return -1;
-}
-
-static size_t
-ec_node_cmd_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-
-       if (node->cmd == NULL)
-               return 0;
-       return 1;
-}
-
-static int
-ec_node_cmd_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-
-       if (i > 0)
-               return -1;
-
-       *child = node->cmd;
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_cmd_type = {
-       .name = "cmd",
-       .schema = ec_node_cmd_schema,
-       .set_config = ec_node_cmd_set_config,
-       .parse = ec_node_cmd_parse,
-       .complete = ec_node_cmd_complete,
-       .size = sizeof(struct ec_node_cmd),
-       .free_priv = ec_node_cmd_free_priv,
-       .get_children_count = ec_node_cmd_get_children_count,
-       .get_child = ec_node_cmd_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_cmd_type);
-
-struct ec_node *__ec_node_cmd(const char *id, const char *cmd, ...)
-{
-       struct ec_config *config = NULL, *children = NULL;
-       struct ec_node *gen_node = NULL;
-       struct ec_node_cmd *node = NULL;
-       va_list ap;
-       int ret;
-
-       /* this block must stay first, it frees the nodes on error */
-       va_start(ap, cmd);
-       children = ec_node_config_node_list_from_vargs(ap);
-       va_end(ap);
-       if (children == NULL)
-               goto fail;
-
-       gen_node = ec_node_from_type(&ec_node_cmd_type, id);
-       if (gen_node == NULL)
-               goto fail;
-       node = (struct ec_node_cmd *)gen_node;
-
-       node->expr = ec_node_cmd_build_expr();
-       if (node->expr == NULL)
-               goto fail;
-
-       node->parser = ec_node_cmd_build_parser(node->expr);
-       if (node->parser == 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, "children", children) < 0) {
-               children = NULL; /* freed */
-               goto fail;
-       }
-       children = 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); /* will also free added children */
-       ec_config_free(children);
-       ec_config_free(config);
-
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_cmd_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = EC_NODE_CMD(EC_NO_ID,
-               "command [option] (subset1, subset2, subset3, subset4) x|y z*",
-               ec_node_int("x", 0, 10, 10),
-               ec_node_int("y", 20, 30, 10)
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "subset1", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 4, "command", "subset3", "subset2",
-                               "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "subset2", "subset3",
-                               "subset1", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 6, "command", "subset3", "subset1",
-                               "subset4", "subset2", "4");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "23");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "option", "23");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "option", "23",
-                               "z", "z");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "command", "15");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       ec_node_free(node);
-
-       node = EC_NODE_CMD(EC_NO_ID, "good morning [count] bob|bobby|michael",
-                       ec_node_int("count", 0, 10, 10));
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 4, "good", "morning", "1", "bob");
-
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "good", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "g", EC_NODE_ENDLIST,
-               "good", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "good", "morning", "", EC_NODE_ENDLIST,
-               "bob", "bobby", "michael", EC_NODE_ENDLIST);
-
-       ec_node_free(node);
-
-       node = EC_NODE_CMD(EC_NO_ID, "[foo [bar]]");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "x");
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_cmd_test = {
-       .name = "node_cmd",
-       .test = ec_node_cmd_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_cmd_test);
diff --git a/lib/ecoli_node_cmd.h b/lib/ecoli_node_cmd.h
deleted file mode 100644 (file)
index 99afc01..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_CMD_
-#define ECOLI_NODE_CMD_
-
-#include <ecoli_node.h>
-
-#define EC_NODE_CMD(args...) __ec_node_cmd(args, EC_NODE_ENDLIST)
-
-struct ec_node *__ec_node_cmd(const char *id, const char *cmd_str, ...);
-
-#endif
diff --git a/lib/ecoli_node_dynamic.c b/lib/ecoli_node_dynamic.c
deleted file mode 100644 (file)
index 8a3edf3..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2017, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_string.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_many.h>
-
-#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_parse *parse,
-               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 = -1;
-
-       child = node->build(parse, node->opaque);
-       if (child == NULL)
-               goto fail;
-
-       /* add the node pointer in the attributes, so it will be freed
-        * when parse is freed */
-       snprintf(key, sizeof(key), "_dyn_%p", child);
-       ret = ec_keyval_set(ec_parse_get_attrs(parse), key, child,
-                       (void *)node_free);
-       if (ret < 0) {
-               child = NULL; /* already freed */
-               goto fail;
-       }
-
-       return ec_node_parse_child(child, parse, strvec);
-
-fail:
-       ec_node_free(child);
-       return ret;
-}
-
-static int
-ec_node_dynamic_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node;
-       struct ec_parse *parse;
-       struct ec_node *child = NULL;
-       void (*node_free)(struct ec_node *) = ec_node_free;
-       char key[64];
-       int ret = -1;
-
-       parse = ec_comp_get_state(comp);
-       child = node->build(parse, node->opaque);
-       if (child == NULL)
-               goto fail;
-
-       /* add the node pointer in the attributes, so it will be freed
-        * when parse is freed */
-       snprintf(key, sizeof(key), "_dyn_%p", child);
-       ret = ec_keyval_set(comp->attrs, key, child,
-                       (void *)node_free);
-       if (ret < 0) {
-               child = NULL; /* already freed */
-               goto fail;
-       }
-
-       return ec_node_complete_child(child, comp, 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_from_type(&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_parse *parse, void *opaque)
-{
-       const struct ec_node *node;
-       struct ec_parse *iter;
-       unsigned int count = 0;
-       char buf[32];
-
-       (void)opaque;
-       for (iter = ec_parse_get_root(parse); iter != NULL;
-            iter = ec_parse_iter_next(iter)) {
-               node = ec_parse_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 testres = 0;
-
-       node = ec_node_many(EC_NO_ID,
-                       ec_node_dynamic(EC_NO_ID, build_counter, NULL),
-                       1, 3);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "count-0", "count-1", "count-2");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0", "count-0");
-
-       /* test completion */
-
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "c", EC_NODE_ENDLIST,
-               "count-0", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "count-0", "", EC_NODE_ENDLIST,
-               "count-1", EC_NODE_ENDLIST,
-               "count-1");
-       ec_node_free(node);
-
-       return testres;
-}
-/* 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
deleted file mode 100644 (file)
index 4f2535e..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2017, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_DYNAMIC_
-#define ECOLI_NODE_DYNAMIC_
-
-struct ec_node;
-struct ec_parse;
-
-/* 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_parse *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_node_empty.c b/lib/ecoli_node_empty.c
deleted file mode 100644 (file)
index 6ce76e8..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_empty.h>
-
-EC_LOG_TYPE_REGISTER(node_empty);
-
-struct ec_node_empty {
-       struct ec_node gen;
-};
-
-static int ec_node_empty_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)state;
-       (void)strvec;
-       return 0;
-}
-
-static struct ec_node_type ec_node_empty_type = {
-       .name = "empty",
-       .parse = ec_node_empty_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_empty),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_empty_type);
-
-/* LCOV_EXCL_START */
-static int ec_node_empty_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("empty", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "foo", "bar");
-       ec_node_free(node);
-
-       /* never completes */
-       node = ec_node("empty", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_empty_test = {
-       .name = "node_empty",
-       .test = ec_node_empty_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_empty_test);
diff --git a/lib/ecoli_node_empty.h b/lib/ecoli_node_empty.h
deleted file mode 100644 (file)
index ed5e32e..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * This node always matches an empty string vector
- */
-
-#ifndef ECOLI_NODE_EMPTY_
-#define ECOLI_NODE_EMPTY_
-
-struct ec_node *ec_node_empty(const char *id);
-
-#endif
diff --git a/lib/ecoli_node_expr.c b/lib/ecoli_node_expr.c
deleted file mode 100644 (file)
index 3b47d8c..0000000
+++ /dev/null
@@ -1,617 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_expr.h>
-
-EC_LOG_TYPE_REGISTER(node_expr);
-
-struct ec_node_expr {
-       struct ec_node gen;
-
-       /* the built node */
-       struct ec_node *child;
-
-       /* the configuration nodes */
-       struct ec_node *val_node;
-       struct ec_node **bin_ops;
-       unsigned int bin_ops_len;
-       struct ec_node **pre_ops;
-       unsigned int pre_ops_len;
-       struct ec_node **post_ops;
-       unsigned int post_ops_len;
-       struct ec_node **open_ops;
-       struct ec_node **close_ops;
-       unsigned int paren_len;
-};
-
-static int ec_node_expr_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (node->child == NULL) {
-               errno = ENOENT;
-               return -1;
-       }
-
-       return ec_node_parse_child(node->child, state, strvec);
-}
-
-static int
-ec_node_expr_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (node->child == NULL) {
-               errno = ENOENT;
-               return -1;
-       }
-
-       return ec_node_complete_child(node->child, comp, strvec);
-}
-
-static void ec_node_expr_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       unsigned int i;
-
-       ec_node_free(node->child);
-       ec_node_free(node->val_node);
-
-       for (i = 0; i < node->bin_ops_len; i++)
-               ec_node_free(node->bin_ops[i]);
-       ec_free(node->bin_ops);
-       for (i = 0; i < node->pre_ops_len; i++)
-               ec_node_free(node->pre_ops[i]);
-       ec_free(node->pre_ops);
-       for (i = 0; i < node->post_ops_len; i++)
-               ec_node_free(node->post_ops[i]);
-       ec_free(node->post_ops);
-       for (i = 0; i < node->paren_len; i++) {
-               ec_node_free(node->open_ops[i]);
-               ec_node_free(node->close_ops[i]);
-       }
-       ec_free(node->open_ops);
-       ec_free(node->close_ops);
-}
-
-static int ec_node_expr_build(struct ec_node_expr *node)
-{
-       struct ec_node *term = NULL, *expr = NULL, *next = NULL,
-               *pre_op = NULL, *post_op = NULL, *ref = NULL,
-               *post = NULL;
-       unsigned int i;
-
-       ec_node_free(node->child);
-       node->child = NULL;
-
-       if (node->val_node == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (node->bin_ops_len == 0 && node->pre_ops_len == 0 &&
-                       node->post_ops_len == 0) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       /*
-        * Example of created grammar:
-        *
-        * pre_op = "!"
-        * post_op = "^"
-        * post = val |
-        *        pre_op expr |
-        *        "(" expr ")"
-        * term = post post_op*
-        * prod = term ( "*" term )*
-        * sum = prod ( "+" prod )*
-        * expr = sum
-        */
-
-       /* we use this as a ref, will be set later */
-       ref = ec_node("seq", "ref");
-       if (ref == NULL)
-               return -1;
-
-       /* prefix unary operators */
-       pre_op = ec_node("or", "pre-op");
-       if (pre_op == NULL)
-               goto fail;
-       for (i = 0; i < node->pre_ops_len; i++) {
-               if (ec_node_or_add(pre_op, ec_node_clone(node->pre_ops[i])) < 0)
-                       goto fail;
-       }
-
-       /* suffix unary operators */
-       post_op = ec_node("or", "post-op");
-       if (post_op == NULL)
-               goto fail;
-       for (i = 0; i < node->post_ops_len; i++) {
-               if (ec_node_or_add(post_op, ec_node_clone(node->post_ops[i])) < 0)
-                       goto fail;
-       }
-
-       post = ec_node("or", "post");
-       if (post == NULL)
-               goto fail;
-       if (ec_node_or_add(post, ec_node_clone(node->val_node)) < 0)
-               goto fail;
-       if (ec_node_or_add(post,
-               EC_NODE_SEQ(EC_NO_ID,
-                       ec_node_clone(pre_op),
-                       ec_node_clone(ref))) < 0)
-               goto fail;
-       for (i = 0; i < node->paren_len; i++) {
-               if (ec_node_or_add(post, EC_NODE_SEQ(EC_NO_ID,
-                                       ec_node_clone(node->open_ops[i]),
-                                       ec_node_clone(ref),
-                                       ec_node_clone(node->close_ops[i]))) < 0)
-                       goto fail;
-       }
-       term = EC_NODE_SEQ("term",
-               ec_node_clone(post),
-               ec_node_many(EC_NO_ID, ec_node_clone(post_op), 0, 0)
-       );
-       if (term == NULL)
-               goto fail;
-
-       for (i = 0; i < node->bin_ops_len; i++) {
-               next = EC_NODE_SEQ("next",
-                       ec_node_clone(term),
-                       ec_node_many(EC_NO_ID,
-                               EC_NODE_SEQ(EC_NO_ID,
-                                       ec_node_clone(node->bin_ops[i]),
-                                       ec_node_clone(term)
-                               ),
-                               0, 0
-                       )
-               );
-               ec_node_free(term);
-               term = next;
-               if (term == NULL)
-                       goto fail;
-       }
-       expr = term;
-       term = NULL;
-
-       /* free the initial references */
-       ec_node_free(pre_op);
-       pre_op = NULL;
-       ec_node_free(post_op);
-       post_op = NULL;
-       ec_node_free(post);
-       post = NULL;
-
-       if (ec_node_seq_add(ref, ec_node_clone(expr)) < 0)
-               goto fail;
-       ec_node_free(ref);
-       ref = NULL;
-
-       node->child = expr;
-
-       return 0;
-
-fail:
-       ec_node_free(term);
-       ec_node_free(expr);
-       ec_node_free(pre_op);
-       ec_node_free(post_op);
-       ec_node_free(post);
-       ec_node_free(ref);
-
-       return -1;
-}
-
-static size_t
-ec_node_expr_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_expr_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_expr_type = {
-       .name = "expr",
-       .parse = ec_node_expr_parse,
-       .complete = ec_node_expr_complete,
-       .size = sizeof(struct ec_node_expr),
-       .free_priv = ec_node_expr_free_priv,
-       .get_children_count = ec_node_expr_get_children_count,
-       .get_child = ec_node_expr_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_expr_type);
-
-int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (val_node == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       ec_node_free(node->val_node);
-       node->val_node = val_node;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(val_node);
-       return -1;
-}
-
-/* add a binary operator */
-int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       struct ec_node **bin_ops;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (node == NULL || op == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       bin_ops = ec_realloc(node->bin_ops,
-               (node->bin_ops_len + 1) * sizeof(*node->bin_ops));
-       if (bin_ops == NULL)
-               goto fail;;
-
-       node->bin_ops = bin_ops;
-       bin_ops[node->bin_ops_len] = op;
-       node->bin_ops_len++;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(op);
-       return -1;
-}
-
-/* add a unary pre-operator */
-int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       struct ec_node **pre_ops;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (node == NULL || op == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       pre_ops = ec_realloc(node->pre_ops,
-               (node->pre_ops_len + 1) * sizeof(*node->pre_ops));
-       if (pre_ops == NULL)
-               goto fail;
-
-       node->pre_ops = pre_ops;
-       pre_ops[node->pre_ops_len] = op;
-       node->pre_ops_len++;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(op);
-       return -1;
-}
-
-/* add a unary post-operator */
-int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       struct ec_node **post_ops;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (node == NULL || op == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       post_ops = ec_realloc(node->post_ops,
-               (node->post_ops_len + 1) * sizeof(*node->post_ops));
-       if (post_ops == NULL)
-               goto fail;
-
-       node->post_ops = post_ops;
-       post_ops[node->post_ops_len] = op;
-       node->post_ops_len++;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(op);
-       return -1;
-}
-
-/* add parenthesis symbols */
-int ec_node_expr_add_parenthesis(struct ec_node *gen_node,
-       struct ec_node *open, struct ec_node *close)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       struct ec_node **open_ops, **close_ops;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (node == NULL || open == NULL || close == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       open_ops = ec_realloc(node->open_ops,
-               (node->paren_len + 1) * sizeof(*node->open_ops));
-       if (open_ops == NULL)
-               goto fail;
-       close_ops = ec_realloc(node->close_ops,
-               (node->paren_len + 1) * sizeof(*node->close_ops));
-       if (close_ops == NULL)
-               goto fail;
-
-       node->open_ops = open_ops;
-       node->close_ops = close_ops;
-       open_ops[node->paren_len] = open;
-       close_ops[node->paren_len] = close;
-       node->paren_len++;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(open);
-       ec_node_free(close);
-       return -1;
-}
-
-enum expr_node_type {
-       NONE,
-       VAL,
-       BIN_OP,
-       PRE_OP,
-       POST_OP,
-       PAREN_OPEN,
-       PAREN_CLOSE,
-};
-
-static enum expr_node_type get_node_type(const struct ec_node *expr_gen_node,
-       const struct ec_node *check)
-{
-       struct ec_node_expr *expr_node = (struct ec_node_expr *)expr_gen_node;
-       size_t i;
-
-       if (check == expr_node->val_node)
-               return VAL;
-
-       for (i = 0; i < expr_node->bin_ops_len; i++) {
-               if (check == expr_node->bin_ops[i])
-                       return BIN_OP;
-       }
-       for (i = 0; i < expr_node->pre_ops_len; i++) {
-               if (check == expr_node->pre_ops[i])
-                       return PRE_OP;
-       }
-       for (i = 0; i < expr_node->post_ops_len; i++) {
-               if (check == expr_node->post_ops[i])
-                       return POST_OP;
-       }
-
-       for (i = 0; i < expr_node->paren_len; i++) {
-               if (check == expr_node->open_ops[i])
-                       return PAREN_OPEN;
-       }
-       for (i = 0; i < expr_node->paren_len; i++) {
-               if (check == expr_node->close_ops[i])
-                       return PAREN_CLOSE;
-       }
-
-       return NONE;
-}
-
-struct result {
-       bool has_val;
-       void *val;
-       const struct ec_parse *op;
-       enum expr_node_type op_type;
-};
-
-/* merge x and y results in x */
-static int merge_results(void *userctx,
-       const struct ec_node_expr_eval_ops *ops,
-       struct result *x, const struct result *y)
-{
-       if (y->has_val == 0 && y->op == NULL)
-               return 0;
-       if (x->has_val == 0 && x->op == NULL) {
-               *x = *y;
-               return 0;
-       }
-
-       if (x->has_val && y->has_val && y->op != NULL) {
-               if (y->op_type == BIN_OP) {
-                       if (ops->eval_bin_op(&x->val, userctx, x->val,
-                                       y->op, y->val) < 0)
-                               return -1;
-
-                       return 0;
-               }
-       }
-
-       if (x->has_val == 0 && x->op != NULL && y->has_val && y->op == NULL) {
-               if (x->op_type == PRE_OP) {
-                       if (ops->eval_pre_op(&x->val, userctx, y->val,
-                                               x->op) < 0)
-                               return -1;
-                       x->has_val = true;
-                       x->op_type = NONE;
-                       x->op = NULL;
-                       return 0;
-               } else if (x->op_type == BIN_OP) {
-                       x->val = y->val;
-                       x->has_val = true;
-                       return 0;
-               }
-       }
-
-       if (x->has_val && x->op == NULL && y->has_val == 0 && y->op != NULL) {
-               if (ops->eval_post_op(&x->val, userctx, x->val, y->op) < 0)
-                       return -1;
-
-               return 0;
-       }
-
-       assert(false); /* we should not get here */
-       return -1;
-}
-
-static int eval_expression(struct result *result,
-       void *userctx,
-       const struct ec_node_expr_eval_ops *ops,
-       const struct ec_node *expr_gen_node,
-       const struct ec_parse *parse)
-
-{
-       struct ec_parse *open = NULL, *close = NULL;
-       struct result child_result;
-       struct ec_parse *child;
-       enum expr_node_type type;
-
-       memset(result, 0, sizeof(*result));
-       memset(&child_result, 0, sizeof(child_result));
-
-       type = get_node_type(expr_gen_node, ec_parse_get_node(parse));
-       if (type == VAL) {
-               if (ops->eval_var(&result->val, userctx, parse) < 0)
-                       goto fail;
-               result->has_val = 1;
-       } else if (type == PRE_OP || type == POST_OP || type == BIN_OP) {
-               result->op = parse;
-               result->op_type = type;
-       }
-
-       EC_PARSE_FOREACH_CHILD(child, parse) {
-
-               type = get_node_type(expr_gen_node, ec_parse_get_node(child));
-               if (type == PAREN_OPEN) {
-                       open = child;
-                       continue;
-               } else if (type == PAREN_CLOSE) {
-                       close = child;
-                       continue;
-               }
-
-               if (eval_expression(&child_result, userctx, ops,
-                       expr_gen_node, child) < 0)
-                       goto fail;
-
-               if (merge_results(userctx, ops, result, &child_result) < 0)
-                       goto fail;
-
-               memset(&child_result, 0, sizeof(child_result));
-       }
-
-       if (open != NULL && close != NULL) {
-               if (ops->eval_parenthesis(&result->val, userctx, open, close,
-                       result->val) < 0)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       if (result->has_val)
-               ops->eval_free(result->val, userctx);
-       if (child_result.has_val)
-               ops->eval_free(child_result.val, userctx);
-       memset(result, 0, sizeof(*result));
-
-       return -1;
-}
-
-int ec_node_expr_eval(void **user_result, const struct ec_node *node,
-       struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops,
-       void *userctx)
-{
-       struct result result;
-
-       if (ops == NULL || ops->eval_var == NULL || ops->eval_pre_op == NULL ||
-                       ops->eval_post_op == NULL || ops->eval_bin_op == NULL ||
-                       ops->eval_parenthesis == NULL ||
-                       ops->eval_free == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (ec_node_check_type(node, &ec_node_expr_type) < 0)
-               return -1;
-
-       if (!ec_parse_matches(parse)) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (eval_expression(&result, userctx, ops, node, parse) < 0)
-               return -1;
-
-       assert(result.has_val);
-       assert(result.op == NULL);
-       *user_result = result.val;
-
-       return 0;
-}
-
-/* the test case is in a separate file ecoli_node_expr_test.c */
diff --git a/lib/ecoli_node_expr.h b/lib/ecoli_node_expr.h
deleted file mode 100644 (file)
index 4f21d81..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_EXPR_
-#define ECOLI_NODE_EXPR_
-
-#include <ecoli_node.h>
-
-/**
- * Callback function type for evaluating a variable
- *
- * @param result
- *   On success, this pointer must be set by the user to point
- *   to a user structure describing the evaluated result.
- * @param userctx
- *   A user-defined context passed to all callback functions, which
- *   can be used to maintain a state or store global information.
- * @param var
- *   The parse result referencing the variable.
- * @return
- *   0 on success (*result must be set), or -errno on error (*result
- *   is undefined).
- */
-typedef int (*ec_node_expr_eval_var_t)(
-       void **result, void *userctx,
-       const struct ec_parse *var);
-
-/**
- * Callback function type for evaluating a prefix-operator
- *
- * @param result
- *   On success, this pointer must be set by the user to point
- *   to a user structure describing the evaluated result.
- * @param userctx
- *   A user-defined context passed to all callback functions, which
- *   can be used to maintain a state or store global information.
- * @param operand
- *   The evaluated expression on which the operation should be applied.
- * @param var
- *   The parse result referencing the operator.
- * @return
- *   0 on success (*result must be set, operand is freed),
- *   or -errno on error (*result is undefined, operand is not freed).
- */
-typedef int (*ec_node_expr_eval_pre_op_t)(
-       void **result, void *userctx,
-       void *operand,
-       const struct ec_parse *operator);
-
-typedef int (*ec_node_expr_eval_post_op_t)(
-       void **result, void *userctx,
-       void *operand,
-       const struct ec_parse *operator);
-
-typedef int (*ec_node_expr_eval_bin_op_t)(
-       void **result, void *userctx,
-       void *operand1,
-       const struct ec_parse *operator,
-       void *operand2);
-
-typedef int (*ec_node_expr_eval_parenthesis_t)(
-       void **result, void *userctx,
-       const struct ec_parse *open_paren,
-       const struct ec_parse *close_paren,
-       void * value);
-
-typedef void (*ec_node_expr_eval_free_t)(
-       void *result, void *userctx);
-
-
-struct ec_node *ec_node_expr(const char *id);
-int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node);
-int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op);
-int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op);
-int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op);
-int ec_node_expr_add_parenthesis(struct ec_node *gen_node,
-       struct ec_node *open, struct ec_node *close);
-
-struct ec_node_expr_eval_ops {
-       ec_node_expr_eval_var_t eval_var;
-       ec_node_expr_eval_pre_op_t eval_pre_op;
-       ec_node_expr_eval_post_op_t eval_post_op;
-       ec_node_expr_eval_bin_op_t eval_bin_op;
-       ec_node_expr_eval_parenthesis_t eval_parenthesis;
-       ec_node_expr_eval_free_t eval_free;
-};
-
-int ec_node_expr_eval(void **result, const struct ec_node *node,
-       struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops,
-       void *userctx);
-
-#endif
diff --git a/lib/ecoli_node_expr_test.c b/lib/ecoli_node_expr_test.c
deleted file mode 100644 (file)
index 93e33a4..0000000
+++ /dev/null
@@ -1,308 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <errno.h>
-#include <limits.h>
-#include <stdint.h>
-#include <assert.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_strvec.h>
-#include <ecoli_test.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_node_int.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_re_lex.h>
-#include <ecoli_node_expr.h>
-
-EC_LOG_TYPE_REGISTER(node_expr);
-
-struct my_eval_result {
-       int val;
-};
-
-static int
-ec_node_expr_test_eval_var(void **result, void *userctx,
-       const struct ec_parse *var)
-{
-       const struct ec_strvec *vec;
-       const struct ec_node *node;
-       struct my_eval_result *eval = NULL;
-       int64_t val;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(var);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       node = ec_parse_get_node(var);
-       if (ec_node_int_getval(node, ec_strvec_val(vec, 0), &val) < 0)
-               return -1;
-
-       eval = ec_malloc(sizeof(*eval));
-       if (eval == NULL)
-               return -1;
-
-       eval->val = val;
-       EC_LOG(EC_LOG_DEBUG, "eval var %d\n", eval->val);
-       *result = eval;
-
-       return 0;
-}
-
-static int
-ec_node_expr_test_eval_pre_op(void **result, void *userctx, void *operand,
-       const struct ec_parse *operator)
-{
-       const struct ec_strvec *vec;
-       struct my_eval_result *eval = operand;;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "!")) {
-               eval->val = !eval->val;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-
-       EC_LOG(EC_LOG_DEBUG, "eval pre_op %d\n", eval->val);
-       *result = eval;
-
-       return 0;
-}
-
-static int
-ec_node_expr_test_eval_post_op(void **result, void *userctx, void *operand,
-       const struct ec_parse *operator)
-{
-       const struct ec_strvec *vec;
-       struct my_eval_result *eval = operand;;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "^")) {
-               eval->val = eval->val * eval->val;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       EC_LOG(EC_LOG_DEBUG, "eval post_op %d\n", eval->val);
-       *result = eval;
-
-       return 0;
-}
-
-static int
-ec_node_expr_test_eval_bin_op(void **result, void *userctx, void *operand1,
-       const struct ec_parse *operator, void *operand2)
-
-{
-       const struct ec_strvec *vec;
-       struct my_eval_result *eval1 = operand1;;
-       struct my_eval_result *eval2 = operand2;;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "+")) {
-               eval1->val = eval1->val + eval2->val;
-       } else if (!strcmp(ec_strvec_val(vec, 0), "*")) {
-               eval1->val = eval1->val * eval2->val;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       EC_LOG(EC_LOG_DEBUG, "eval bin_op %d\n", eval1->val);
-       ec_free(eval2);
-       *result = eval1;
-
-       return 0;
-}
-
-static int
-ec_node_expr_test_eval_parenthesis(void **result, void *userctx,
-       const struct ec_parse *open_paren,
-       const struct ec_parse *close_paren,
-       void *value)
-{
-       (void)userctx;
-       (void)open_paren;
-       (void)close_paren;
-
-       EC_LOG(EC_LOG_DEBUG, "eval paren\n");
-       *result = value;
-
-       return 0;
-}
-
-static void
-ec_node_expr_test_eval_free(void *result, void *userctx)
-{
-       (void)userctx;
-       ec_free(result);
-}
-
-static const struct ec_node_expr_eval_ops test_ops = {
-       .eval_var = ec_node_expr_test_eval_var,
-       .eval_pre_op = ec_node_expr_test_eval_pre_op,
-       .eval_post_op = ec_node_expr_test_eval_post_op,
-       .eval_bin_op = ec_node_expr_test_eval_bin_op,
-       .eval_parenthesis = ec_node_expr_test_eval_parenthesis,
-       .eval_free = ec_node_expr_test_eval_free,
-};
-
-static int ec_node_expr_test_eval(struct ec_node *lex_node,
-       const struct ec_node *expr_node,
-       const char *str, int val)
-{
-       struct ec_parse *p;
-       void *result;
-       struct my_eval_result *eval;
-       int ret;
-
-       p = ec_node_parse(lex_node, str);
-       if (p == NULL)
-               return -1;
-
-       ret = ec_node_expr_eval(&result, expr_node, p, &test_ops, NULL);
-       ec_parse_free(p);
-       if (ret < 0)
-               return -1;
-
-       /* the parse value is an integer */
-       eval = result;
-       assert(eval != NULL);
-
-       EC_LOG(EC_LOG_DEBUG, "result: %d (expected %d)\n", eval->val, val);
-       if (eval->val == val)
-               ret = 0;
-       else
-               ret = -1;
-
-       ec_free(eval);
-
-       return ret;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_expr_testcase(void)
-{
-       struct ec_node *node = NULL, *lex_node = NULL;
-       int testres = 0;
-
-       node = ec_node("expr", "my_expr");
-       if (node == NULL)
-               return -1;
-
-       ec_node_expr_set_val_node(node, ec_node_int(EC_NO_ID, 0, UCHAR_MAX, 0));
-       ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "+"));
-       ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "*"));
-       ec_node_expr_add_pre_op(node, ec_node_str(EC_NO_ID, "!"));  /* not */
-       ec_node_expr_add_post_op(node, ec_node_str(EC_NO_ID, "^")); /* square */
-       ec_node_expr_add_parenthesis(node, ec_node_str(EC_NO_ID, "("),
-               ec_node_str(EC_NO_ID, ")"));
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "*");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1", "*");
-       testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "+", "!", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "^", "+", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "*", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "+", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 7, "1", "*", "1", "*", "1", "*",
-                               "1");
-       testres |= EC_TEST_CHECK_PARSE(
-               node, 10, "!", "(", "1", "*", "(", "1", "+", "1", ")", ")");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "+", "!", "1", "^");
-
-       /* prepend a lexer to the expression node */
-       lex_node = ec_node_re_lex(EC_NO_ID, ec_node_clone(node));
-       if (lex_node == NULL)
-               goto fail;
-
-       testres |= ec_node_re_lex_add(lex_node, "[0-9]+", 1); /* vars */
-       testres |= ec_node_re_lex_add(lex_node, "[+*!^()]", 1); /* operators */
-       testres |= ec_node_re_lex_add(lex_node, "[      ]+", 0); /* spaces */
-
-       /* valid expressions */
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^ + 1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1 + 4 * (2 + 3^)^");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1)");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "3*!3+!3*(2+ 2)");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!!(!1)^ + !(4 + (2*3))");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1 + 1)^ * 1^");
-
-       /* invalid expressions */
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "()");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "(");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, ")");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "+1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+*1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+(1*1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+!1!1)");
-
-       testres |= ec_node_expr_test_eval(lex_node, node, "1^", 1);
-       testres |= ec_node_expr_test_eval(lex_node, node, "2^", 4);
-       testres |= ec_node_expr_test_eval(lex_node, node, "!1", 0);
-       testres |= ec_node_expr_test_eval(lex_node, node, "!0", 1);
-
-       testres |= ec_node_expr_test_eval(lex_node, node, "1+1", 2);
-       testres |= ec_node_expr_test_eval(lex_node, node, "1+2+3", 6);
-       testres |= ec_node_expr_test_eval(lex_node, node, "1+1*2", 4);
-       testres |= ec_node_expr_test_eval(lex_node, node, "2 * 2^", 8);
-       testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !0)^ * !0^", 4);
-       testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !1) * 3", 3);
-
-       ec_node_free(node);
-       ec_node_free(lex_node);
-
-       return testres;
-
-fail:
-       ec_node_free(lex_node);
-       ec_node_free(node);
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_expr_test = {
-       .name = "node_expr",
-       .test = ec_node_expr_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_expr_test);
diff --git a/lib/ecoli_node_file.c b/lib/ecoli_node_file.c
deleted file mode 100644 (file)
index 001dcb6..0000000
+++ /dev/null
@@ -1,425 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-#include <dirent.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_string.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_file.h>
-
-EC_LOG_TYPE_REGISTER(node_file);
-
-struct ec_node_file {
-       struct ec_node gen;
-
-       /* below functions pointers are only useful for test */
-       int (*lstat)(const char *pathname, struct stat *buf);
-       DIR *(*opendir)(const char *name);
-       struct dirent *(*readdir)(DIR *dirp);
-       int (*closedir)(DIR *dirp);
-       int (*dirfd)(DIR *dirp);
-       int (*fstatat)(int dirfd, const char *pathname, struct stat *buf,
-               int flags);
-};
-
-static int
-ec_node_file_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)state;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-/*
- * Almost the same than dirname (3) and basename (3) except that:
- * - it always returns a substring of the given path, which can
- *   be empty.
- * - the behavior is different when the path finishes with a '/'
- * - the path argument is not modified
- * - the outputs are allocated and must be freed with ec_free().
- *
- *   path       dirname   basename       split_path
- *   /usr/lib   /usr      lib          /usr/     lib
- *   /usr/      /         usr          /usr/
- *   usr        .         usr                    usr
- *   /          /         /            /
- *   .          .         .                      .
- *   ..         .         ..                     ..
- */
-static int split_path(const char *path, char **dname_p, char **bname_p)
-{
-       char *last_slash;
-       size_t dirlen;
-       char *dname, *bname;
-
-       *dname_p = NULL;
-       *bname_p = NULL;
-
-       last_slash = strrchr(path, '/');
-       if (last_slash == NULL)
-               dirlen = 0;
-       else
-               dirlen = last_slash - path + 1;
-
-       dname = ec_strdup(path);
-       if (dname == NULL)
-               return -1;
-       dname[dirlen] = '\0';
-
-       bname = ec_strdup(path + dirlen);
-       if (bname == NULL) {
-               ec_free(dname);
-               return -1;
-       }
-
-       *dname_p = dname;
-       *bname_p = bname;
-
-       return 0;
-}
-
-static int
-ec_node_file_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_file *node = (struct ec_node_file *)gen_node;
-       char *dname = NULL, *bname = NULL, *effective_dir;
-       struct ec_comp_item *item = NULL;
-       enum ec_comp_type type;
-       struct stat st, st2;
-       const char *input;
-       size_t bname_len;
-       struct dirent *de = NULL;
-       DIR *dir = NULL;
-       char *comp_str = NULL;
-       char *disp_str = NULL;
-       int is_dir = 0;
-
-       /*
-        * Example with this file tree:
-        * /
-        * ├── dir1
-        * │   ├── file1
-        * │   ├── file2
-        * │   └── subdir
-        * │       └── file3
-        * ├── dir2
-        * │   └── file4
-        * └── file5
-        *
-        * Input     Output completions
-        *   /       [dir1/, dir2/, file5]
-        *   /d      [dir1/, dir2/]
-        *   /f      [file5]
-        *   /dir1/  [file1, file2, subdir/]
-        *
-        *
-        *
-        */
-
-       if (ec_strvec_len(strvec) != 1)
-               return 0;
-
-       input = ec_strvec_val(strvec, 0);
-       if (split_path(input, &dname, &bname) < 0)
-               return -1;
-
-       if (strcmp(dname, "") == 0)
-               effective_dir = ".";
-       else
-               effective_dir = dname;
-
-       if (node->lstat(effective_dir, &st) < 0)
-               goto fail;
-       if (!S_ISDIR(st.st_mode))
-               goto out;
-
-       dir = node->opendir(effective_dir);
-       if (dir == NULL)
-               goto fail;
-
-       bname_len = strlen(bname);
-       while (1) {
-               int save_errno = errno;
-
-               errno = 0;
-               de = node->readdir(dir);
-               if (de == NULL) {
-                       if (errno == 0) {
-                               errno = save_errno;
-                               goto out;
-                       } else {
-                               goto fail;
-                       }
-               }
-
-               if (!ec_str_startswith(de->d_name, bname))
-                       continue;
-               if (bname[0] != '.' && de->d_name[0] == '.')
-                       continue;
-
-               /* add '/' if it's a dir */
-               if (de->d_type == DT_DIR) {
-                       is_dir = 1;
-               } else if (de->d_type == DT_UNKNOWN) {
-                       int dir_fd = node->dirfd(dir);
-
-                       if (dir_fd < 0)
-                               goto fail;
-                       if (node->fstatat(dir_fd, de->d_name, &st2, 0) < 0)
-                               goto fail;
-                       if (S_ISDIR(st2.st_mode))
-                               is_dir = 1;
-                       else
-                               is_dir = 0;
-               } else {
-                       is_dir = 0;
-               }
-
-               if (is_dir) {
-                       type = EC_COMP_PARTIAL;
-                       if (ec_asprintf(&comp_str, "%s%s/", input,
-                                       &de->d_name[bname_len]) < 0)
-                               goto fail;
-                       if (ec_asprintf(&disp_str, "%s/", de->d_name) < 0)
-                               goto fail;
-               } else {
-                       type = EC_COMP_FULL;
-                       if (ec_asprintf(&comp_str, "%s%s", input,
-                                       &de->d_name[bname_len]) < 0)
-                               goto fail;
-                       if (ec_asprintf(&disp_str, "%s", de->d_name) < 0)
-                               goto fail;
-               }
-               if (ec_comp_add_item(comp, gen_node, &item,
-                                               type, input, comp_str) < 0)
-                       goto out;
-
-               /* fix the display string: we don't want to display the full
-                * path. */
-               if (ec_comp_item_set_display(item, disp_str) < 0)
-                       goto out;
-
-               item = NULL;
-               ec_free(comp_str);
-               comp_str = NULL;
-               ec_free(disp_str);
-               disp_str = NULL;
-       }
-out:
-       ec_free(comp_str);
-       ec_free(disp_str);
-       ec_free(dname);
-       ec_free(bname);
-       if (dir != NULL)
-               node->closedir(dir);
-
-       return 0;
-
-fail:
-       ec_free(comp_str);
-       ec_free(disp_str);
-       ec_free(dname);
-       ec_free(bname);
-       if (dir != NULL)
-               node->closedir(dir);
-
-       return -1;
-}
-
-static int
-ec_node_file_init_priv(struct ec_node *gen_node)
-{
-       struct ec_node_file *node = (struct ec_node_file *)gen_node;
-
-       node->lstat = lstat;
-       node->opendir = opendir;
-       node->readdir = readdir;
-       node->dirfd = dirfd;
-       node->fstatat = fstatat;
-
-       return 0;
-}
-
-static struct ec_node_type ec_node_file_type = {
-       .name = "file",
-       .parse = ec_node_file_parse,
-       .complete = ec_node_file_complete,
-       .size = sizeof(struct ec_node_file),
-       .init_priv = ec_node_file_init_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_file_type);
-
-/* LCOV_EXCL_START */
-static int
-test_lstat(const char *pathname, struct stat *buf)
-{
-       if (!strcmp(pathname, "/tmp/toto/")) {
-               struct stat st = { .st_mode = S_IFDIR };
-               memcpy(buf, &st, sizeof(*buf));
-               return 0;
-       }
-
-       errno = ENOENT;
-       return -1;
-}
-
-static DIR *
-test_opendir(const char *name)
-{
-       int *p;
-
-       if (strcmp(name, "/tmp/toto/")) {
-               errno = ENOENT;
-               return NULL;
-       }
-
-       p = malloc(sizeof(int));
-       if (p)
-               *p = 0;
-
-       return (DIR *)p;
-}
-
-static struct dirent *
-test_readdir(DIR *dirp)
-{
-       static struct dirent de[] = {
-               { .d_type = DT_DIR, .d_name = ".." },
-               { .d_type = DT_DIR, .d_name = "." },
-               { .d_type = DT_REG, .d_name = "bar" },
-               { .d_type = DT_UNKNOWN, .d_name = "bar2" },
-               { .d_type = DT_REG, .d_name = "foo" },
-               { .d_type = DT_DIR, .d_name = "titi" },
-               { .d_type = DT_UNKNOWN, .d_name = "tutu" },
-               { .d_name = "" },
-       };
-       int *p = (int *)dirp;
-       struct dirent *ret = &de[*p];
-
-       if (!strcmp(ret->d_name, ""))
-               return NULL;
-
-       *p = *p + 1;
-
-       return ret;
-}
-
-static int
-test_closedir(DIR *dirp)
-{
-       free(dirp);
-       return 0;
-}
-
-static int
-test_dirfd(DIR *dirp)
-{
-       int *p = (int *)dirp;
-       return *p;
-}
-
-static int
-test_fstatat(int dirfd, const char *pathname, struct stat *buf,
-       int flags)
-{
-       (void)dirfd;
-       (void)flags;
-
-       if (!strcmp(pathname, "bar2")) {
-               struct stat st = { .st_mode = S_IFREG };
-               memcpy(buf, &st, sizeof(*buf));
-               return 0;
-       } else if (!strcmp(pathname, "tutu")) {
-               struct stat st = { .st_mode = S_IFDIR };
-               memcpy(buf, &st, sizeof(*buf));
-               return 0;
-       }
-
-       errno = ENOENT;
-       return -1;
-}
-
-static int
-ec_node_file_override_functions(struct ec_node *gen_node)
-{
-       struct ec_node_file *node = (struct ec_node_file *)gen_node;
-
-       node->lstat = test_lstat;
-       node->opendir = test_opendir;
-       node->readdir = test_readdir;
-       node->closedir = test_closedir;
-       node->dirfd = test_dirfd;
-       node->fstatat = test_fstatat;
-
-       return 0;
-}
-
-static int ec_node_file_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("file", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       ec_node_file_override_functions(node);
-
-       /* any string matches */
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-
-       /* test completion */
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "/tmp/toto/t", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE_PARTIAL(node,
-               "/tmp/toto/t", EC_NODE_ENDLIST,
-               "/tmp/toto/titi/", "/tmp/toto/tutu/", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "/tmp/toto/f", EC_NODE_ENDLIST,
-               "/tmp/toto/foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "/tmp/toto/b", EC_NODE_ENDLIST,
-               "/tmp/toto/bar", "/tmp/toto/bar2", EC_NODE_ENDLIST);
-
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_file_test = {
-       .name = "node_file",
-       .test = ec_node_file_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_file_test);
diff --git a/lib/ecoli_node_file.h b/lib/ecoli_node_file.h
deleted file mode 100644 (file)
index 5760902..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_FILE_
-#define ECOLI_NODE_FILE_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_file(const char *id, const char *file);
-
-/* file is duplicated */
-int ec_node_file_set_str(struct ec_node *node, const char *file);
-
-#endif
diff --git a/lib/ecoli_node_helper.c b/lib/ecoli_node_helper.c
deleted file mode 100644 (file)
index 9ec7e89..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/types.h>
-#include <sys/queue.h>
-#include <stdarg.h>
-#include <stddef.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_config.h>
-#include <ecoli_node.h>
-#include <ecoli_node_helper.h>
-
-struct ec_node **
-ec_node_config_node_list_to_table(const struct ec_config *config,
-                               size_t *len)
-{
-       struct ec_node **table = NULL;
-       struct ec_config *child;
-       size_t n, i;
-
-       *len = 0;
-
-       if (config == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       if (ec_config_get_type(config) != EC_CONFIG_TYPE_LIST) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       n = 0;
-       TAILQ_FOREACH(child, &config->list, next) {
-               if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
-                       errno = EINVAL;
-                       return NULL;
-               }
-               n++;
-       }
-
-       table = ec_malloc(n * sizeof(*table));
-       if (table == NULL)
-               goto fail;
-
-       n = 0;
-       TAILQ_FOREACH(child, &config->list, next) {
-               table[n] = ec_node_clone(child->node);
-               n++;
-       }
-
-       *len = n;
-
-       return table;
-
-fail:
-       if (table != NULL) {
-               for (i = 0; i < n; i++)
-                       ec_node_free(table[i]);
-       }
-       ec_free(table);
-
-       return NULL;
-}
-
-struct ec_config *
-ec_node_config_node_list_from_vargs(va_list ap)
-{
-       struct ec_config *list = NULL;
-       struct ec_node *node = va_arg(ap, struct ec_node *);
-
-       list = ec_config_list();
-       if (list == NULL)
-               goto fail;
-
-       for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *)) {
-               if (node == NULL)
-                       goto fail;
-
-               if (ec_config_list_add(list, ec_config_node(node)) < 0)
-                       goto fail;
-       }
-
-       return list;
-
-fail:
-       for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *))
-               ec_node_free(node);
-       ec_config_free(list);
-
-       return NULL;
-}
diff --git a/lib/ecoli_node_helper.h b/lib/ecoli_node_helper.h
deleted file mode 100644 (file)
index 9dbf519..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Helpers that are commonly used in nodes.
- */
-
-#ifndef ECOLI_NODE_HELPERS_
-#define ECOLI_NODE_HELPERS
-
-struct ec_node;
-
-/**
- * Build a node table from a node list in a ec_config.
- *
- * The function takes a node configuration as parameter, which must be a
- * node list. From it, a node table is built. A reference is taken for
- * each node.
- *
- * On error, no reference is taken.
- *
- * @param config
- *   The configuration (type must be a list of nodes). If it is
- *   NULL, an error is returned.
- * @param len
- *   The length of the allocated table on success, or 0 on error.
- * @return
- *   The allocated node table, that must be freed by the caller:
- *   each entry must be freed with ec_node_free() and the table
- *   with ec_free(). On error, NULL is returned and errno is set.
- */
-struct ec_node **
-ec_node_config_node_list_to_table(const struct ec_config *config,
-                               size_t *len);
-
-/**
- * Build a list of config nodes from variable arguments.
- *
- * The va_list argument is a list of pointer to ec_node structures,
- * terminated with EC_NODE_ENDLIST.
- *
- * This helper is used by nodes that contain a list of nodes,
- * like "seq", "or", ...
- *
- * @param ap
- *   List of pointer to ec_node structures, terminated with
- *   EC_NODE_ENDLIST.
- * @return
- *   A pointer to an ec_config structure. In this case, the
- *   nodes will be freed when the config structure will be freed.
- *   On error, NULL is returned (and errno is set), and the
- *   nodes are freed.
- */
-struct ec_config *
-ec_node_config_node_list_from_vargs(va_list ap);
-
-#endif
diff --git a/lib/ecoli_node_int.c b/lib/ecoli_node_int.c
deleted file mode 100644 (file)
index 9b56e22..0000000
+++ /dev/null
@@ -1,501 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <limits.h>
-#include <ctype.h>
-#include <errno.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_int.h>
-#include <ecoli_test.h>
-
-EC_LOG_TYPE_REGISTER(node_int);
-
-/* common to int and uint */
-struct ec_node_int_uint {
-       struct ec_node gen;
-       bool is_signed;
-       bool check_min;
-       bool check_max;
-       union {
-               int64_t min;
-               uint64_t umin;
-       };
-       union {
-               int64_t max;
-               uint64_t umax;
-       };
-       unsigned int base;
-};
-
-/* XXX to utils.c ? */
-static int parse_llint(struct ec_node_int_uint *node, const char *str,
-       int64_t *val)
-{
-       char *endptr;
-       int save_errno = errno;
-
-       errno = 0;
-       *val = strtoll(str, &endptr, node->base);
-
-       if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
-                       (errno != 0 && *val == 0))
-               return -1;
-
-       if (node->check_min && *val < node->min) {
-               errno = ERANGE;
-               return -1;
-       }
-
-       if (node->check_max && *val > node->max) {
-               errno = ERANGE;
-               return -1;
-       }
-
-       if (*endptr != 0) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       errno = save_errno;
-       return 0;
-}
-
-static int parse_ullint(struct ec_node_int_uint *node, const char *str,
-                       uint64_t *val)
-{
-       char *endptr;
-       int save_errno = errno;
-
-       /* since a negative input is silently converted to a positive
-        * one by strtoull(), first check that it is positive */
-       if (strchr(str, '-'))
-               return -1;
-
-       errno = 0;
-       *val = strtoull(str, &endptr, node->base);
-
-       if ((errno == ERANGE && *val == ULLONG_MAX) ||
-                       (errno != 0 && *val == 0))
-               return -1;
-
-       if (node->check_min && *val < node->umin)
-               return -1;
-
-       if (node->check_max && *val > node->umax)
-               return -1;
-
-       if (*endptr != 0)
-               return -1;
-
-       errno = save_errno;
-       return 0;
-}
-
-static int ec_node_int_uint_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       const char *str;
-       uint64_t u64;
-       int64_t i64;
-
-       (void)state;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       str = ec_strvec_val(strvec, 0);
-       if (node->is_signed) {
-               if (parse_llint(node, str, &i64) < 0)
-                       return EC_PARSE_NOMATCH;
-       } else {
-               if (parse_ullint(node, str, &u64) < 0)
-                       return EC_PARSE_NOMATCH;
-       }
-       return 1;
-}
-
-static int
-ec_node_uint_init_priv(struct ec_node *gen_node)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-
-       node->is_signed = true;
-
-       return 0;
-}
-
-static const struct ec_config_schema ec_node_int_schema[] = {
-       {
-               .key = "min",
-               .desc = "The minimum valid value (included).",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .key = "max",
-               .desc = "The maximum valid value (included).",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .key = "base",
-               .desc = "The base to use. If unset or 0, try to guess.",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_int_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       const struct ec_config *min_value = NULL;
-       const struct ec_config *max_value = NULL;
-       const struct ec_config *base_value = NULL;
-       char *s = NULL;
-
-       min_value = ec_config_dict_get(config, "min");
-       max_value = ec_config_dict_get(config, "max");
-       base_value = ec_config_dict_get(config, "base");
-
-       if (min_value && max_value && min_value->i64 > max_value->i64) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (min_value != NULL) {
-               node->check_min = true;
-               node->min = min_value->i64;
-       } else {
-               node->check_min = false;
-       }
-       if (max_value != NULL) {
-               node->check_max = true;
-               node->max = max_value->i64;
-       } else {
-               node->check_min = false;
-       }
-       if (base_value != NULL)
-               node->base = base_value->u64;
-       else
-               node->base = 0;
-
-       return 0;
-
-fail:
-       ec_free(s);
-       return -1;
-}
-
-static struct ec_node_type ec_node_int_type = {
-       .name = "int",
-       .schema = ec_node_int_schema,
-       .set_config = ec_node_int_set_config,
-       .parse = ec_node_int_uint_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_int_uint),
-       .init_priv = ec_node_uint_init_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_int_type);
-
-struct ec_node *ec_node_int(const char *id, int64_t min,
-       int64_t max, unsigned int base)
-{
-       struct ec_config *config = NULL;
-       struct ec_node *gen_node = NULL;
-       int ret;
-
-       gen_node = ec_node_from_type(&ec_node_int_type, id);
-       if (gen_node == NULL)
-               return NULL;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "min", ec_config_i64(min));
-       if (ret < 0)
-               goto fail;
-       ret = ec_config_dict_set(config, "max", ec_config_i64(max));
-       if (ret < 0)
-               goto fail;
-       ret = ec_config_dict_set(config, "base", ec_config_u64(base));
-       if (ret < 0)
-               goto fail;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL;
-       if (ret < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(gen_node);
-       return NULL;
-}
-
-static const struct ec_config_schema ec_node_uint_schema[] = {
-       {
-               .key = "min",
-               .desc = "The minimum valid value (included).",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .key = "max",
-               .desc = "The maximum valid value (included).",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .key = "base",
-               .desc = "The base to use. If unset or 0, try to guess.",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_uint_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       const struct ec_config *min_value = NULL;
-       const struct ec_config *max_value = NULL;
-       const struct ec_config *base_value = NULL;
-       char *s = NULL;
-
-       min_value = ec_config_dict_get(config, "min");
-       max_value = ec_config_dict_get(config, "max");
-       base_value = ec_config_dict_get(config, "base");
-
-       if (min_value && max_value && min_value->u64 > max_value->u64) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (min_value != NULL) {
-               node->check_min = true;
-               node->min = min_value->u64;
-       } else {
-               node->check_min = false;
-       }
-       if (max_value != NULL) {
-               node->check_max = true;
-               node->max = max_value->u64;
-       } else {
-               node->check_min = false;
-       }
-       if (base_value != NULL)
-               node->base = base_value->u64;
-       else
-               node->base = 0;
-
-       return 0;
-
-fail:
-       ec_free(s);
-       return -1;
-}
-
-static struct ec_node_type ec_node_uint_type = {
-       .name = "uint",
-       .schema = ec_node_uint_schema,
-       .set_config = ec_node_uint_set_config,
-       .parse = ec_node_int_uint_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_int_uint),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_uint_type);
-
-struct ec_node *ec_node_uint(const char *id, uint64_t min,
-       uint64_t max, unsigned int base)
-{
-       struct ec_config *config = NULL;
-       struct ec_node *gen_node = NULL;
-       int ret;
-
-       gen_node = ec_node_from_type(&ec_node_uint_type, id);
-       if (gen_node == NULL)
-               return NULL;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "min", ec_config_u64(min));
-       if (ret < 0)
-               goto fail;
-       ret = ec_config_dict_set(config, "max", ec_config_u64(max));
-       if (ret < 0)
-               goto fail;
-       ret = ec_config_dict_set(config, "base", ec_config_u64(base));
-       if (ret < 0)
-               goto fail;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL;
-       if (ret < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(gen_node);
-       return NULL;
-}
-
-int ec_node_int_getval(const struct ec_node *gen_node, const char *str,
-                       int64_t *result)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       int ret;
-
-       ret = ec_node_check_type(gen_node, &ec_node_int_type);
-       if (ret < 0)
-               return ret;
-
-       if (parse_llint(node, str, result) < 0)
-               return -1;
-
-       return 0;
-}
-
-int ec_node_uint_getval(const struct ec_node *gen_node, const char *str,
-                       uint64_t *result)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       int ret;
-
-       ret = ec_node_check_type(gen_node, &ec_node_uint_type);
-       if (ret < 0)
-               return ret;
-
-       if (parse_ullint(node, str, result) < 0)
-               return -1;
-
-       return 0;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_int_testcase(void)
-{
-       struct ec_parse *p;
-       struct ec_node *node;
-       const char *s;
-       int testres = 0;
-       uint64_t u64;
-       int64_t i64;
-
-       node = ec_node_uint(EC_NO_ID, 1, 256, 0);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "256", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "0x100");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, " 1");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "-1");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x101");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x100000000000000000");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "4r");
-
-       p = ec_node_parse(node, "1");
-       s = ec_strvec_val(ec_parse_strvec(p), 0);
-       testres |= EC_TEST_CHECK(s != NULL &&
-               ec_node_uint_getval(node, s, &u64) == 0 &&
-               u64 == 1, "bad integer value");
-       ec_parse_free(p);
-
-       p = ec_node_parse(node, "10");
-       s = ec_strvec_val(ec_parse_strvec(p), 0);
-       testres |= EC_TEST_CHECK(s != NULL &&
-               ec_node_uint_getval(node, s, &u64) == 0 &&
-               u64 == 10, "bad integer value");
-       ec_parse_free(p);
-       ec_node_free(node);
-
-       node = ec_node_int(EC_NO_ID, -1, LLONG_MAX, 16);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "0");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "-1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "7fffffffffffffff");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "0x7fffffffffffffff");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x8000000000000000");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "-2");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "4r");
-
-       p = ec_node_parse(node, "10");
-       s = ec_strvec_val(ec_parse_strvec(p), 0);
-       testres |= EC_TEST_CHECK(s != NULL &&
-               ec_node_int_getval(node, s, &i64) == 0 &&
-               i64 == 16, "bad integer value");
-       ec_parse_free(p);
-       ec_node_free(node);
-
-       node = ec_node_int(EC_NO_ID, LLONG_MIN, 0, 10);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "0");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "-1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "-9223372036854775808");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x0");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "1");
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_int(EC_NO_ID, 0, 10, 0);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "1", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_int_test = {
-       .name = "node_int",
-       .test = ec_node_int_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_int_test);
diff --git a/lib/ecoli_node_int.h b/lib/ecoli_node_int.h
deleted file mode 100644 (file)
index b64c60c..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_INT_
-#define ECOLI_NODE_INT_
-
-#include <stdint.h>
-
-#include <ecoli_node.h>
-
-/* ec_node("int", ...) can be used too
- * default is no limit, base 10 */
-
-struct ec_node *ec_node_int(const char *id, int64_t min,
-                       int64_t max, unsigned int base);
-
-int ec_node_int_getval(const struct ec_node *node, const char *str,
-                       int64_t *result);
-
-
-
-struct ec_node *ec_node_uint(const char *id, uint64_t min,
-                       uint64_t max, unsigned int base);
-
-int ec_node_uint_getval(const struct ec_node *node, const char *str,
-                       uint64_t *result);
-
-
-#endif
diff --git a/lib/ecoli_node_many.c b/lib/ecoli_node_many.c
deleted file mode 100644 (file)
index 1c91f85..0000000
+++ /dev/null
@@ -1,300 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_option.h>
-#include <ecoli_node_many.h>
-
-EC_LOG_TYPE_REGISTER(node_many);
-
-struct ec_node_many {
-       struct ec_node gen;
-       unsigned int min;
-       unsigned int max;
-       struct ec_node *child;
-};
-
-static int ec_node_many_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-       struct ec_parse *child_parse;
-       struct ec_strvec *childvec = NULL;
-       size_t off = 0, count;
-       int ret;
-
-       for (count = 0; node->max == 0 || count < node->max; count++) {
-               childvec = ec_strvec_ndup(strvec, off,
-                       ec_strvec_len(strvec) - off);
-               if (childvec == NULL)
-                       goto fail;
-
-               ret = ec_node_parse_child(node->child, state, childvec);
-               if (ret < 0)
-                       goto fail;
-
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if (ret == EC_PARSE_NOMATCH)
-                       break;
-
-               /* it matches an empty strvec, no need to continue */
-               if (ret == 0) {
-                       child_parse = ec_parse_get_last_child(state);
-                       ec_parse_unlink_child(state, child_parse);
-                       ec_parse_free(child_parse);
-                       break;
-               }
-
-               off += ret;
-       }
-
-       if (count < node->min) {
-               ec_parse_free_children(state);
-               return EC_PARSE_NOMATCH;
-       }
-
-       return off;
-
-fail:
-       ec_strvec_free(childvec);
-       return -1;
-}
-
-static int
-__ec_node_many_complete(struct ec_node_many *node, unsigned int max,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_parse *parse = ec_comp_get_state(comp);
-       struct ec_strvec *childvec = NULL;
-       unsigned int i;
-       int ret;
-
-       /* first, try to complete with the child node */
-       ret = ec_node_complete_child(node->child, comp, strvec);
-       if (ret < 0)
-               goto fail;
-
-       /* we're done, we reached the max number of nodes */
-       if (max == 1)
-               return 0;
-
-       /* if there is a maximum, decrease it before recursion */
-       if (max != 0)
-               max--;
-
-       /* then, if the node matches the beginning of the strvec, try to
-        * complete the rest */
-       for (i = 0; i < ec_strvec_len(strvec); i++) {
-               childvec = ec_strvec_ndup(strvec, 0, i);
-               if (childvec == NULL)
-                       goto fail;
-
-               ret = ec_node_parse_child(node->child, parse, childvec);
-               if (ret < 0)
-                       goto fail;
-
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if ((unsigned int)ret != i) {
-                       if (ret != EC_PARSE_NOMATCH)
-                               ec_parse_del_last_child(parse);
-                       continue;
-               }
-
-               childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i);
-               if (childvec == NULL) {
-                       ec_parse_del_last_child(parse);
-                       goto fail;
-               }
-
-               ret = __ec_node_many_complete(node, max, comp, childvec);
-               ec_parse_del_last_child(parse);
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if (ret < 0)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       ec_strvec_free(childvec);
-       return -1;
-}
-
-static int
-ec_node_many_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-
-       return __ec_node_many_complete(node, node->max, comp,
-                               strvec);
-}
-
-static void ec_node_many_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-
-       ec_node_free(node->child);
-}
-
-static size_t
-ec_node_many_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_many_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_many_type = {
-       .name = "many",
-       .parse = ec_node_many_parse,
-       .complete = ec_node_many_complete,
-       .size = sizeof(struct ec_node_many),
-       .free_priv = ec_node_many_free_priv,
-       .get_children_count = ec_node_many_get_children_count,
-       .get_child = ec_node_many_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_many_type);
-
-struct ec_node *ec_node_many(const char *id, struct ec_node *child,
-       unsigned int min, unsigned int max)
-{
-       struct ec_node_many *node = NULL;
-
-       if (child == NULL)
-               return NULL;
-
-       node = (struct ec_node_many *)ec_node_from_type(&ec_node_many_type, id);
-       if (node == NULL) {
-               ec_node_free(child);
-               return NULL;
-       }
-
-       node->child = child;
-       node->min = min;
-       node->max = max;
-
-       return &node->gen;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_many_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 0, 0);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       ec_node_free(node);
-
-       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 0);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-       ec_node_free(node);
-
-       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 2);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 2, 4);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "foo", "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "foo", "foo", "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "foo", "foo", "foo", "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_many_test = {
-       .name = "node_many",
-       .test = ec_node_many_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_many_test);
diff --git a/lib/ecoli_node_many.h b/lib/ecoli_node_many.h
deleted file mode 100644 (file)
index 14250d1..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_MANY_
-#define ECOLI_NODE_MANY_
-
-/*
- * if min == max == 0, there is no limit
- */
-struct ec_node *ec_node_many(const char *id, struct ec_node *child,
-       unsigned int min, unsigned int max);
-
-#endif
diff --git a/lib/ecoli_node_none.c b/lib/ecoli_node_none.c
deleted file mode 100644 (file)
index dba9ebf..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_none.h>
-
-EC_LOG_TYPE_REGISTER(node_none);
-
-struct ec_node_none {
-       struct ec_node gen;
-};
-
-static int ec_node_none_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)state;
-       (void)strvec;
-
-       return EC_PARSE_NOMATCH;
-}
-
-static int
-ec_node_none_complete(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)comp;
-       (void)strvec;
-
-       return 0;
-}
-
-static struct ec_node_type ec_node_none_type = {
-       .name = "none",
-       .parse = ec_node_none_parse,
-       .complete = ec_node_none_complete,
-       .size = sizeof(struct ec_node_none),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_none_type);
-
-/* LCOV_EXCL_START */
-static int ec_node_none_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("none", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-       ec_node_free(node);
-
-       /* never completes */
-       node = ec_node("none", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_none_test = {
-       .name = "node_none",
-       .test = ec_node_none_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_none_test);
diff --git a/lib/ecoli_node_none.h b/lib/ecoli_node_none.h
deleted file mode 100644 (file)
index 842f211..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * This node does not match anything
- */
-
-#ifndef ECOLI_NODE_ANY_
-#define ECOLI_NODE_ANY_
-
-#endif
diff --git a/lib/ecoli_node_once.c b/lib/ecoli_node_once.c
deleted file mode 100644 (file)
index 6309878..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_once.h>
-
-EC_LOG_TYPE_REGISTER(node_once);
-
-struct ec_node_once {
-       struct ec_node gen;
-       struct ec_node *child;
-};
-
-static unsigned int
-count_node(struct ec_parse *parse, const struct ec_node *node)
-{
-       struct ec_parse *child;
-       unsigned int count = 0;
-
-       if (parse == NULL)
-               return 0;
-
-       if (ec_parse_get_node(parse) == node)
-               count++;
-
-       EC_PARSE_FOREACH_CHILD(child, parse)
-               count += count_node(child, node);
-
-       return count;
-}
-
-static int
-ec_node_once_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-       unsigned int count;
-
-       /* count the number of occurences of the node: if already parsed,
-        * do not match
-        */
-       count = count_node(ec_parse_get_root(state), node->child);
-       if (count > 0)
-               return EC_PARSE_NOMATCH;
-
-       return ec_node_parse_child(node->child, state, strvec);
-}
-
-static int
-ec_node_once_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-       struct ec_parse *parse = ec_comp_get_state(comp);
-       unsigned int count;
-       int ret;
-
-       /* count the number of occurences of the node: if already parsed,
-        * do not match
-        */
-       count = count_node(ec_parse_get_root(parse), node->child);
-       if (count > 0)
-               return 0;
-
-       ret = ec_node_complete_child(node->child, comp, strvec);
-       if (ret < 0)
-               return ret;
-
-       return 0;
-}
-
-static void ec_node_once_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-
-       ec_node_free(node->child);
-}
-
-static size_t
-ec_node_once_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_once_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_once_type = {
-       .name = "once",
-       .parse = ec_node_once_parse,
-       .complete = ec_node_once_complete,
-       .size = sizeof(struct ec_node_once),
-       .free_priv = ec_node_once_free_priv,
-       .get_children_count = ec_node_once_get_children_count,
-       .get_child = ec_node_once_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_once_type);
-
-int ec_node_once_set(struct ec_node *gen_node, struct ec_node *child)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-
-       if (gen_node == NULL || child == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (ec_node_check_type(gen_node, &ec_node_once_type) < 0)
-               goto fail;
-
-       node->child = child;
-
-       return 0;
-
-fail:
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *ec_node_once(const char *id, struct ec_node *child)
-{
-       struct ec_node *gen_node = NULL;
-
-       if (child == NULL)
-               return NULL;
-
-       gen_node = ec_node_from_type(&ec_node_once_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       ec_node_once_set(gen_node, child);
-       child = NULL;
-
-       return gen_node;
-
-fail:
-       ec_node_free(child);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_once_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_many(EC_NO_ID,
-                       EC_NODE_OR(EC_NO_ID,
-                               ec_node_once(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")),
-                               ec_node_str(EC_NO_ID, "bar")
-                               ), 0, 0
-               );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "foox");
-
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "b", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", "", EC_NODE_ENDLIST,
-               "foo", "bar", EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_once_test = {
-       .name = "node_once",
-       .test = ec_node_once_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_once_test);
diff --git a/lib/ecoli_node_once.h b/lib/ecoli_node_once.h
deleted file mode 100644 (file)
index a610a83..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_ONCE_
-#define ECOLI_NODE_ONCE_
-
-#include <ecoli_node.h>
-
-/* This node behaves like its child, but prevent from parsing it several
- * times.
- *
- * Example:
- *   many(
- *     or(
- *       once(str("foo")),
- *       str("bar")))
- *
- *   Matches: [], ["foo", "bar"], ["bar", "bar"], ["foo", "bar", "bar"], ...
- *   But not: ["foo", "foo"], ["foo", "bar", "foo"], ...
- */
-
-/* on error, child is *not* freed */
-struct ec_node *ec_node_once(const char *id, struct ec_node *child);
-
-/* on error, child is *not* freed */
-int ec_node_once_set(struct ec_node *node, struct ec_node *child);
-
-#endif
diff --git a/lib/ecoli_node_option.c b/lib/ecoli_node_option.c
deleted file mode 100644 (file)
index ab4f352..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_str.h>
-#include <ecoli_test.h>
-#include <ecoli_node_option.h>
-
-EC_LOG_TYPE_REGISTER(node_option);
-
-struct ec_node_option {
-       struct ec_node gen;
-       struct ec_node *child;
-};
-
-static int
-ec_node_option_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-       int ret;
-
-       ret = ec_node_parse_child(node->child, state, strvec);
-       if (ret < 0)
-               return ret;
-
-       if (ret == EC_PARSE_NOMATCH)
-               return 0;
-
-       return ret;
-}
-
-static int
-ec_node_option_complete(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       return ec_node_complete_child(node->child, comp, strvec);
-}
-
-static void ec_node_option_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       ec_node_free(node->child);
-}
-
-static size_t
-ec_node_option_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_option_get_child(const struct ec_node *gen_node, size_t i,
-       struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_option_type = {
-       .name = "option",
-       .parse = ec_node_option_parse,
-       .complete = ec_node_option_complete,
-       .size = sizeof(struct ec_node_option),
-       .free_priv = ec_node_option_free_priv,
-       .get_children_count = ec_node_option_get_children_count,
-       .get_child = ec_node_option_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_option_type);
-
-int ec_node_option_set(struct ec_node *gen_node, struct ec_node *child)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       if (gen_node == NULL || child == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (ec_node_check_type(gen_node, &ec_node_option_type) < 0)
-               goto fail;
-
-       node->child = child;
-
-       return 0;
-
-fail:
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *ec_node_option(const char *id, struct ec_node *child)
-{
-       struct ec_node *gen_node = NULL;
-
-       if (child == NULL)
-               goto fail;
-
-       gen_node = ec_node_from_type(&ec_node_option_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       ec_node_option_set(gen_node, child);
-       child = NULL;
-
-       return gen_node;
-
-fail:
-       ec_node_free(child);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_option_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"));
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"));
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "b", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_option_test = {
-       .name = "node_option",
-       .test = ec_node_option_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_option_test);
diff --git a/lib/ecoli_node_option.h b/lib/ecoli_node_option.h
deleted file mode 100644 (file)
index 9d67480..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_OPTION_
-#define ECOLI_NODE_OPTION_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_option(const char *id, struct ec_node *node);
-int ec_node_option_set(struct ec_node *gen_node, struct ec_node *child);
-
-#endif
diff --git a/lib/ecoli_node_or.c b/lib/ecoli_node_or.c
deleted file mode 100644 (file)
index 2bf8fc7..0000000
+++ /dev/null
@@ -1,347 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_node_helper.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_str.h>
-#include <ecoli_test.h>
-
-EC_LOG_TYPE_REGISTER(node_or);
-
-struct ec_node_or {
-       struct ec_node gen;
-       struct ec_node **table;
-       size_t len;
-};
-
-static int
-ec_node_or_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       unsigned int i;
-       int ret;
-
-       for (i = 0; i < node->len; i++) {
-               ret = ec_node_parse_child(node->table[i], state, strvec);
-               if (ret == EC_PARSE_NOMATCH)
-                       continue;
-               return ret;
-       }
-
-       return EC_PARSE_NOMATCH;
-}
-
-static int
-ec_node_or_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       int ret;
-       size_t n;
-
-       for (n = 0; n < node->len; n++) {
-               ret = ec_node_complete_child(node->table[n],
-                                       comp, strvec);
-               if (ret < 0)
-                       return ret;
-       }
-
-       return 0;
-}
-
-static void ec_node_or_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       size_t i;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = NULL;
-       node->len = 0;
-}
-
-static const struct ec_config_schema ec_node_or_subschema[] = {
-       {
-               .desc = "A child node which is part of the choice.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema ec_node_or_schema[] = {
-       {
-               .key = "children",
-               .desc = "The list of children nodes defining the choice "
-               "elements.",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = ec_node_or_subschema,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_or_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       struct ec_node **table = NULL;
-       size_t i, len = 0;
-
-       table = ec_node_config_node_list_to_table(
-               ec_config_dict_get(config, "children"), &len);
-       if (table == NULL)
-               goto fail;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = table;
-       node->len = len;
-
-       return 0;
-
-fail:
-       for (i = 0; i < len; i++)
-               ec_node_free(table[i]);
-       ec_free(table);
-       return -1;
-}
-
-static size_t
-ec_node_or_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       return node->len;
-}
-
-static int
-ec_node_or_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-
-       if (i >= node->len)
-               return -1;
-
-       *child = node->table[i];
-       /* each child node is referenced twice: once in the config and
-        * once in the node->table[] */
-       *refs = 2;
-       return 0;
-}
-
-static struct ec_node_type ec_node_or_type = {
-       .name = "or",
-       .schema = ec_node_or_schema,
-       .set_config = ec_node_or_set_config,
-       .parse = ec_node_or_parse,
-       .complete = ec_node_or_complete,
-       .size = sizeof(struct ec_node_or),
-       .free_priv = ec_node_or_free_priv,
-       .get_children_count = ec_node_or_get_children_count,
-       .get_child = ec_node_or_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_or_type);
-
-int ec_node_or_add(struct ec_node *gen_node, struct ec_node *child)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL, *children;
-       int ret;
-
-       assert(node != NULL);
-
-       /* XXX factorize this code in a helper */
-
-       if (ec_node_check_type(gen_node, &ec_node_or_type) < 0)
-               goto fail;
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       children = ec_config_dict_get(config, "children");
-       if (children == NULL) {
-               children = ec_config_list();
-               if (children == NULL)
-                       goto fail;
-
-               if (ec_config_dict_set(config, "children", children) < 0)
-                       goto fail; /* children list is freed on error */
-       }
-
-       if (ec_config_list_add(children, ec_config_node(child)) < 0) {
-               child = NULL;
-               goto fail;
-       }
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *__ec_node_or(const char *id, ...)
-{
-       struct ec_config *config = NULL, *children = NULL;
-       struct ec_node *gen_node = NULL;
-       struct ec_node *child;
-       va_list ap;
-       int ret;
-
-       va_start(ap, id);
-       child = va_arg(ap, struct ec_node *);
-
-       gen_node = ec_node_from_type(&ec_node_or_type, id);
-       if (gen_node == NULL)
-               goto fail_free_children;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail_free_children;
-
-       children = ec_config_list();
-       if (children == NULL)
-               goto fail_free_children;
-
-       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) {
-               if (child == NULL)
-                       goto fail_free_children;
-
-               if (ec_config_list_add(children, ec_config_node(child)) < 0) {
-                       child = NULL;
-                       goto fail_free_children;
-               }
-       }
-
-       if (ec_config_dict_set(config, "children", children) < 0) {
-               children = NULL; /* freed */
-               goto fail;
-       }
-       children = NULL;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       va_end(ap);
-
-       return gen_node;
-
-fail_free_children:
-       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *))
-               ec_node_free(child);
-fail:
-       ec_node_free(gen_node); /* will also free added children */
-       ec_config_free(children);
-       ec_config_free(config);
-       va_end(ap);
-
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_or_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = EC_NODE_OR(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_str(EC_NO_ID, "bar")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, " ");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foox");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "toto");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       ec_node_free(node);
-
-       /* test completion */
-       node = EC_NODE_OR(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_str(EC_NO_ID, "bar"),
-               ec_node_str(EC_NO_ID, "bar2"),
-               ec_node_str(EC_NO_ID, "toto"),
-               ec_node_str(EC_NO_ID, "titi")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "b", EC_NODE_ENDLIST,
-               "bar", "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", EC_NODE_ENDLIST,
-               "bar", "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "t", EC_NODE_ENDLIST,
-               "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "to", EC_NODE_ENDLIST,
-               "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_or_test = {
-       .name = "node_or",
-       .test = ec_node_or_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_or_test);
diff --git a/lib/ecoli_node_or.h b/lib/ecoli_node_or.h
deleted file mode 100644 (file)
index db115b2..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_OR_
-#define ECOLI_NODE_OR_
-
-#include <ecoli_node.h>
-
-#define EC_NODE_OR(args...) __ec_node_or(args, EC_NODE_ENDLIST)
-
-/* list must be terminated with EC_NODE_ENDLIST */
-/* all nodes given in the list will be freed when freeing this one */
-/* avoid using this function directly, prefer the macro EC_NODE_OR() or
- * ec_node_or() + ec_node_or_add() */
-struct ec_node *__ec_node_or(const char *id, ...);
-
-struct ec_node *ec_node_or(const char *id);
-
-/* child is consumed */
-int ec_node_or_add(struct ec_node *node, struct ec_node *child);
-
-
-#endif
diff --git a/lib/ecoli_node_re.c b/lib/ecoli_node_re.c
deleted file mode 100644 (file)
index 6ac5182..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <regex.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_re.h>
-
-EC_LOG_TYPE_REGISTER(node_re);
-
-struct ec_node_re {
-       struct ec_node gen;
-       char *re_str;
-       regex_t re;
-};
-
-static int
-ec_node_re_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_re *node = (struct ec_node_re *)gen_node;
-       const char *str;
-       regmatch_t pos;
-
-       (void)state;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       str = ec_strvec_val(strvec, 0);
-       if (regexec(&node->re, str, 1, &pos, 0) != 0)
-               return EC_PARSE_NOMATCH;
-       if (pos.rm_so != 0 || pos.rm_eo != (int)strlen(str))
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-static void ec_node_re_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_re *node = (struct ec_node_re *)gen_node;
-
-       if (node->re_str != NULL) {
-               ec_free(node->re_str);
-               regfree(&node->re);
-       }
-}
-
-static struct ec_node_type ec_node_re_type = {
-       .name = "re",
-       .parse = ec_node_re_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_re),
-       .free_priv = ec_node_re_free_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_re_type);
-
-int ec_node_re_set_regexp(struct ec_node *gen_node, const char *str)
-{
-       struct ec_node_re *node = (struct ec_node_re *)gen_node;
-       char *str_copy = NULL;
-       regex_t re;
-       int ret;
-
-       EC_CHECK_ARG(str != NULL, -1, EINVAL);
-
-       str_copy = ec_strdup(str);
-       if (str_copy == NULL)
-               goto fail;
-
-       ret = regcomp(&re, str_copy, REG_EXTENDED);
-       if (ret != 0) {
-               if (ret == REG_ESPACE)
-                       errno = ENOMEM;
-               else
-                       errno = EINVAL;
-               goto fail;
-       }
-
-       if (node->re_str != NULL) {
-               ec_free(node->re_str);
-               regfree(&node->re);
-       }
-       node->re_str = str_copy;
-       node->re = re;
-
-       return 0;
-
-fail:
-       ec_free(str_copy);
-       return -1;
-}
-
-struct ec_node *ec_node_re(const char *id, const char *re_str)
-{
-       struct ec_node *gen_node = NULL;
-
-       gen_node = ec_node_from_type(&ec_node_re_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       if (ec_node_re_set_regexp(gen_node, re_str) < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_re_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_re(EC_NO_ID, "fo+|bar");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_re_test = {
-       .name = "node_re",
-       .test = ec_node_re_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_re_test);
diff --git a/lib/ecoli_node_re.h b/lib/ecoli_node_re.h
deleted file mode 100644 (file)
index bc3a317..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_RE_
-#define ECOLI_NODE_RE_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_re(const char *id, const char *str);
-
-/* re is duplicated */
-int ec_node_re_set_regexp(struct ec_node *node, const char *re);
-
-#endif
diff --git a/lib/ecoli_node_re_lex.c b/lib/ecoli_node_re_lex.c
deleted file mode 100644 (file)
index f5a6c37..0000000
+++ /dev/null
@@ -1,308 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <string.h>
-#include <regex.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_complete.h>
-#include <ecoli_parse.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_int.h>
-#include <ecoli_node_re_lex.h>
-
-EC_LOG_TYPE_REGISTER(node_re_lex);
-
-struct regexp_pattern {
-       char *pattern;
-       regex_t r;
-       bool keep;
-};
-
-struct ec_node_re_lex {
-       struct ec_node gen;
-       struct ec_node *child;
-       struct regexp_pattern *table;
-       size_t len;
-};
-
-static struct ec_strvec *
-tokenize(struct regexp_pattern *table, size_t table_len, const char *str)
-{
-       struct ec_strvec *strvec = NULL;
-       char *dup = NULL;
-       char c;
-       size_t len, off = 0;
-       size_t i;
-       int ret;
-       regmatch_t pos;
-
-       dup = ec_strdup(str);
-       if (dup == NULL)
-               goto fail;
-
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       len = strlen(dup);
-       while (off < len) {
-               for (i = 0; i < table_len; i++) {
-                       ret = regexec(&table[i].r, &dup[off], 1, &pos, 0);
-                       if (ret != 0)
-                               continue;
-                       if (pos.rm_so != 0 || pos.rm_eo == 0) {
-                               ret = -1;
-                               continue;
-                       }
-
-                       if (table[i].keep == 0)
-                               break;
-
-                       c = dup[pos.rm_eo + off];
-                       dup[pos.rm_eo + off] = '\0';
-                       EC_LOG(EC_LOG_DEBUG, "re_lex match <%s>\n", &dup[off]);
-                       if (ec_strvec_add(strvec, &dup[off]) < 0)
-                               goto fail;
-
-                       dup[pos.rm_eo + off] = c;
-                       break;
-               }
-
-               if (ret != 0)
-                       goto fail;
-
-               off += pos.rm_eo;
-       }
-
-       ec_free(dup);
-       return strvec;
-
-fail:
-       ec_free(dup);
-       ec_strvec_free(strvec);
-       return NULL;
-}
-
-static int
-ec_node_re_lex_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-       struct ec_strvec *new_vec = NULL;
-       struct ec_parse *child_parse;
-       const char *str;
-       int ret;
-
-       if (ec_strvec_len(strvec) == 0) {
-               new_vec = ec_strvec();
-       } else {
-               str = ec_strvec_val(strvec, 0);
-               new_vec = tokenize(node->table, node->len, str);
-       }
-       if (new_vec == NULL)
-               goto fail;
-
-       ret = ec_node_parse_child(node->child, state, new_vec);
-       if (ret < 0)
-               goto fail;
-
-       if ((unsigned)ret == ec_strvec_len(new_vec)) {
-               ret = 1;
-       } else if (ret != EC_PARSE_NOMATCH) {
-               child_parse = ec_parse_get_last_child(state);
-               ec_parse_unlink_child(state, child_parse);
-               ec_parse_free(child_parse);
-               ret = EC_PARSE_NOMATCH;
-       }
-
-       ec_strvec_free(new_vec);
-       new_vec = NULL;
-
-       return ret;
-
- fail:
-       ec_strvec_free(new_vec);
-       return -1;
-}
-
-static void ec_node_re_lex_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-       unsigned int i;
-
-       ec_node_free(node->child);
-       for (i = 0; i < node->len; i++) {
-               ec_free(node->table[i].pattern);
-               regfree(&node->table[i].r);
-       }
-
-       ec_free(node->table);
-}
-
-static size_t
-ec_node_re_lex_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_re_lex_get_child(const struct ec_node *gen_node, size_t i,
-                       struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_re_lex_type = {
-       .name = "re_lex",
-       .parse = ec_node_re_lex_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_re_lex),
-       .free_priv = ec_node_re_lex_free_priv,
-       .get_children_count = ec_node_re_lex_get_children_count,
-       .get_child = ec_node_re_lex_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_re_lex_type);
-
-int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-       struct regexp_pattern *table;
-       int ret;
-       char *pat_dup = NULL;
-
-       pat_dup = ec_strdup(pattern);
-       if (pat_dup == NULL)
-               goto fail;
-
-       table = ec_realloc(node->table, sizeof(*table) * (node->len + 1));
-       if (table == NULL)
-               goto fail;
-
-       ret = regcomp(&table[node->len].r, pattern, REG_EXTENDED);
-       if (ret != 0) {
-               EC_LOG(EC_LOG_ERR,
-                       "Regular expression <%s> compilation failed: %d\n",
-                       pattern, ret);
-               if (ret == REG_ESPACE)
-                       errno = ENOMEM;
-               else
-                       errno = EINVAL;
-
-               goto fail;
-       }
-
-       table[node->len].pattern = pat_dup;
-       table[node->len].keep = keep;
-       node->len++;
-       node->table = table;
-
-       return 0;
-
-fail:
-       ec_free(pat_dup);
-       return -1;
-}
-
-struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child)
-{
-       struct ec_node_re_lex *node = NULL;
-
-       if (child == NULL)
-               return NULL;
-
-       node = (struct ec_node_re_lex *)ec_node_from_type(&ec_node_re_lex_type, id);
-       if (node == NULL) {
-               ec_node_free(child);
-               return NULL;
-       }
-
-       node->child = child;
-
-       return &node->gen;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_re_lex_testcase(void)
-{
-       struct ec_node *node;
-       int ret, testres = 0;
-
-       node = ec_node_re_lex(EC_NO_ID,
-               ec_node_many(EC_NO_ID,
-                       EC_NODE_OR(EC_NO_ID,
-                               ec_node_str(EC_NO_ID, "foo"),
-                               ec_node_str(EC_NO_ID, "bar"),
-                               ec_node_int(EC_NO_ID, 0, 1000, 0)
-                       ), 0, 0
-               )
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-
-       ret = ec_node_re_lex_add(node, "[a-zA-Z]+", 1);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "[0-9]+", 1);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "=", 1);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "-", 1);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "\\+", 1);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "[       ]+", 0);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       if (ret != 0) {
-               EC_LOG(EC_LOG_ERR, "cannot add regexp to node\n");
-               ec_node_free(node);
-               return -1;
-       }
-
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo bar  324 bar234");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar324");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
-
-       /* no completion */
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_re_lex_test = {
-       .name = "node_re_lex",
-       .test = ec_node_re_lex_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_re_lex_test);
diff --git a/lib/ecoli_node_re_lex.h b/lib/ecoli_node_re_lex.h
deleted file mode 100644 (file)
index 632627c..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_RE_LEX_
-#define ECOLI_NODE_RE_LEX_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child);
-
-int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep);
-
-#endif
diff --git a/lib/ecoli_node_seq.c b/lib/ecoli_node_seq.c
deleted file mode 100644 (file)
index ff0c5de..0000000
+++ /dev/null
@@ -1,442 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_helper.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_option.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_seq.h>
-
-EC_LOG_TYPE_REGISTER(node_seq);
-
-struct ec_node_seq {
-       struct ec_node gen;
-       struct ec_node **table;
-       size_t len;
-};
-
-static int
-ec_node_seq_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       struct ec_strvec *childvec = NULL;
-       size_t len = 0;
-       unsigned int i;
-       int ret;
-
-       for (i = 0; i < node->len; i++) {
-               childvec = ec_strvec_ndup(strvec, len,
-                       ec_strvec_len(strvec) - len);
-               if (childvec == NULL)
-                       goto fail;
-
-               ret = ec_node_parse_child(node->table[i], state, childvec);
-               if (ret < 0)
-                       goto fail;
-
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if (ret == EC_PARSE_NOMATCH) {
-                       ec_parse_free_children(state);
-                       return EC_PARSE_NOMATCH;
-               }
-
-               len += ret;
-       }
-
-       return len;
-
-fail:
-       ec_strvec_free(childvec);
-       return -1;
-}
-
-static int
-__ec_node_seq_complete(struct ec_node **table, size_t table_len,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_parse *parse = ec_comp_get_state(comp);
-       struct ec_strvec *childvec = NULL;
-       unsigned int i;
-       int ret;
-
-       if (table_len == 0)
-               return 0;
-
-       /*
-        * Example of completion for a sequence node = [n1,n2] and an
-        * input = [a,b,c,d]:
-        *
-        * result = complete(n1, [a,b,c,d]) +
-        *    complete(n2, [b,c,d]) if n1 matches [a] +
-        *    complete(n2, [c,d]) if n1 matches [a,b] +
-        *    complete(n2, [d]) if n1 matches [a,b,c] +
-        *    complete(n2, []) if n1 matches [a,b,c,d]
-        */
-
-       /* first, try to complete with the first node of the table */
-       ret = ec_node_complete_child(table[0], comp, strvec);
-       if (ret < 0)
-               goto fail;
-
-       /* then, if the first node of the table matches the beginning of the
-        * strvec, try to complete the rest */
-       for (i = 0; i < ec_strvec_len(strvec); i++) {
-               childvec = ec_strvec_ndup(strvec, 0, i);
-               if (childvec == NULL)
-                       goto fail;
-
-               ret = ec_node_parse_child(table[0], parse, childvec);
-               if (ret < 0)
-                       goto fail;
-
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if ((unsigned int)ret != i) {
-                       if (ret != EC_PARSE_NOMATCH)
-                               ec_parse_del_last_child(parse);
-                       continue;
-               }
-
-               childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i);
-               if (childvec == NULL) {
-                       ec_parse_del_last_child(parse);
-                       goto fail;
-               }
-
-               ret = __ec_node_seq_complete(&table[1],
-                                       table_len - 1,
-                                       comp, childvec);
-               ec_parse_del_last_child(parse);
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if (ret < 0)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       ec_strvec_free(childvec);
-       return -1;
-}
-
-static int
-ec_node_seq_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-
-       return __ec_node_seq_complete(node->table, node->len, comp,
-                               strvec);
-}
-
-static void ec_node_seq_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       size_t i;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = NULL;
-       node->len = 0;
-}
-
-static const struct ec_config_schema ec_node_seq_subschema[] = {
-       {
-               .desc = "A child node which is part of the sequence.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema ec_node_seq_schema[] = {
-       {
-               .key = "children",
-               .desc = "The list of children nodes, to be parsed in sequence.",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = ec_node_seq_subschema,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_seq_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       struct ec_node **table = NULL;
-       size_t i, len = 0;
-
-       table = ec_node_config_node_list_to_table(
-               ec_config_dict_get(config, "children"), &len);
-       if (table == NULL)
-               goto fail;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = table;
-       node->len = len;
-
-       return 0;
-
-fail:
-       for (i = 0; i < len; i++)
-               ec_node_free(table[i]);
-       ec_free(table);
-       return -1;
-}
-
-static size_t
-ec_node_seq_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       return node->len;
-}
-
-static int
-ec_node_seq_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-
-       if (i >= node->len)
-               return -1;
-
-       *child = node->table[i];
-       /* each child node is referenced twice: once in the config and
-        * once in the node->table[] */
-       *refs = 2;
-       return 0;
-}
-
-static struct ec_node_type ec_node_seq_type = {
-       .name = "seq",
-       .schema = ec_node_seq_schema,
-       .set_config = ec_node_seq_set_config,
-       .parse = ec_node_seq_parse,
-       .complete = ec_node_seq_complete,
-       .size = sizeof(struct ec_node_seq),
-       .free_priv = ec_node_seq_free_priv,
-       .get_children_count = ec_node_seq_get_children_count,
-       .get_child = ec_node_seq_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_seq_type);
-
-int ec_node_seq_add(struct ec_node *gen_node, struct ec_node *child)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL, *children;
-       int ret;
-
-       assert(node != NULL);
-
-       /* XXX factorize this code in a helper */
-
-       if (ec_node_check_type(gen_node, &ec_node_seq_type) < 0)
-               goto fail;
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       children = ec_config_dict_get(config, "children");
-       if (children == NULL) {
-               children = ec_config_list();
-               if (children == NULL)
-                       goto fail;
-
-               if (ec_config_dict_set(config, "children", children) < 0)
-                       goto fail; /* children list is freed on error */
-       }
-
-       if (ec_config_list_add(children, ec_config_node(child)) < 0) {
-               child = NULL;
-               goto fail;
-       }
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *__ec_node_seq(const char *id, ...)
-{
-       struct ec_config *config = NULL, *children = NULL;
-       struct ec_node *gen_node = NULL;
-       struct ec_node *child;
-       va_list ap;
-       int ret;
-
-       va_start(ap, id);
-       child = va_arg(ap, struct ec_node *);
-
-       gen_node = ec_node_from_type(&ec_node_seq_type, id);
-       if (gen_node == NULL)
-               goto fail_free_children;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail_free_children;
-
-       children = ec_config_list();
-       if (children == NULL)
-               goto fail_free_children;
-
-       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) {
-               if (child == NULL)
-                       goto fail_free_children;
-
-               if (ec_config_list_add(children, ec_config_node(child)) < 0) {
-                       child = NULL;
-                       goto fail_free_children;
-               }
-       }
-
-       if (ec_config_dict_set(config, "children", children) < 0) {
-               children = NULL; /* freed */
-               goto fail;
-       }
-       children = NULL;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       va_end(ap);
-
-       return gen_node;
-
-fail_free_children:
-       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *))
-               ec_node_free(child);
-fail:
-       ec_node_free(gen_node); /* will also free added children */
-       ec_config_free(children);
-       ec_config_free(config);
-       va_end(ap);
-
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_seq_testcase(void)
-{
-       struct ec_node *node = NULL;
-       int testres = 0;
-
-       node = EC_NODE_SEQ(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_str(EC_NO_ID, "bar")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "toto");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foox", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "barx");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "", "foo");
-
-       testres |= (ec_node_seq_add(node, ec_node_str(EC_NO_ID, "grr")) < 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "grr");
-
-       ec_node_free(node);
-
-       /* test completion */
-       node = EC_NODE_SEQ(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "toto")),
-               ec_node_str(EC_NO_ID, "bar")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "", EC_NODE_ENDLIST,
-               "bar", "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "t", EC_NODE_ENDLIST,
-               "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "b", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "bar", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foobarx", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_seq_test = {
-       .name = "node_seq",
-       .test = ec_node_seq_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_seq_test);
diff --git a/lib/ecoli_node_seq.h b/lib/ecoli_node_seq.h
deleted file mode 100644 (file)
index 21c96b1..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_SEQ_
-#define ECOLI_NODE_SEQ_
-
-#include <ecoli_node.h>
-
-#define EC_NODE_SEQ(args...) __ec_node_seq(args, EC_NODE_ENDLIST)
-
-/* list must be terminated with EC_NODE_ENDLIST */
-/* all nodes given in the list will be freed when freeing this one */
-/* avoid using this function directly, prefer the macro EC_NODE_SEQ() or
- * ec_node_seq() + ec_node_seq_add() */
-struct ec_node *__ec_node_seq(const char *id, ...);
-
-struct ec_node *ec_node_seq(const char *id);
-
-/* child is consumed */
-int ec_node_seq_add(struct ec_node *node, struct ec_node *child);
-
-#endif
diff --git a/lib/ecoli_node_sh_lex.c b/lib/ecoli_node_sh_lex.c
deleted file mode 100644 (file)
index e27f21b..0000000
+++ /dev/null
@@ -1,491 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_string.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_option.h>
-#include <ecoli_node_sh_lex.h>
-
-EC_LOG_TYPE_REGISTER(node_sh_lex);
-
-struct ec_node_sh_lex {
-       struct ec_node gen;
-       struct ec_node *child;
-};
-
-static size_t eat_spaces(const char *str)
-{
-       size_t i = 0;
-
-       /* skip spaces */
-       while (isblank(str[i]))
-               i++;
-
-       return i;
-}
-
-/*
- * 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 = 1, d = 0;
-       char quote = str[0];
-       char *dst;
-       int closed = 0;
-
-       dst = ec_malloc(n);
-       if (dst == NULL) {
-               errno = ENOMEM;
-               return NULL;
-       }
-
-       /* copy string and remove quotes */
-       while (s < n && d < n && str[s] != '\0') {
-               if (str[s] == '\\' && str[s+1] == quote) {
-                       dst[d++] = quote;
-                       s += 2;
-                       continue;
-               }
-               if (str[s] == '\\' && str[s+1] == '\\') {
-                       dst[d++] = '\\';
-                       s += 2;
-                       continue;
-               }
-               if (str[s] == quote) {
-                       s++;
-                       closed = 1;
-                       break;
-               }
-               dst[d++] = str[s++];
-       }
-
-       /* not enough room in dst buffer (should not happen) */
-       if (d >= n) {
-               ec_free(dst);
-               errno = EMSGSIZE;
-               return NULL;
-       }
-
-       /* quote not closed */
-       if (closed == 0) {
-               if (missing_quote != NULL)
-                       *missing_quote = str[0];
-               if (allow_missing_quote == 0) {
-                       ec_free(dst);
-                       errno = EBADMSG;
-                       return NULL;
-               }
-       }
-       dst[d++] = '\0';
-
-       return dst;
-}
-
-static size_t eat_quoted_str(const char *str)
-{
-       size_t i = 0;
-       char quote = str[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 size_t eat_str(const char *str)
-{
-       size_t i = 0;
-
-       /* eat chars until we find a quote, space, or end of string  */
-       while (!isblank(str[i]) && str[i] != '\0' &&
-                       str[i] != '"' && str[i] != '\'')
-               i++;
-
-       return i;
-}
-
-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;
-
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       while (str[off] != '\0') {
-               if (missing_quote != NULL)
-                       *missing_quote = '\0';
-               len = eat_spaces(&str[off]);
-               if (len > 0)
-                       last_is_space = 1;
-               off += len;
-
-               len = 0;
-               suboff = off;
-               while (str[suboff] != '\0') {
-                       if (missing_quote != NULL)
-                               *missing_quote = '\0';
-                       last_is_space = 0;
-                       if (str[suboff] == '"' || str[suboff] == '\'') {
-                               sublen = eat_quoted_str(&str[suboff]);
-                               word = unquote_str(&str[suboff], sublen,
-                                       allow_missing_quote, missing_quote);
-                       } else {
-                               sublen = eat_str(&str[suboff]);
-                               if (sublen == 0)
-                                       break;
-                               word = ec_strndup(&str[suboff], sublen);
-                       }
-
-                       if (word == NULL)
-                               goto fail;
-
-                       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;
-               }
-
-               off += len;
-       }
-
-       /* in completion mode, append an empty string in the vector if
-        * the input string ends with space */
-       if (completion && last_is_space) {
-               if (ec_strvec_add(strvec, "") < 0)
-                       goto fail;
-       }
-
-       return strvec;
-
- fail:
-       ec_free(word);
-       ec_free(concat);
-       ec_strvec_free(strvec);
-       return NULL;
-}
-
-static int
-ec_node_sh_lex_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-       struct ec_strvec *new_vec = NULL;
-       struct ec_parse *child_parse;
-       const char *str;
-       int ret;
-
-       if (ec_strvec_len(strvec) == 0) {
-               new_vec = ec_strvec();
-       } else {
-               str = ec_strvec_val(strvec, 0);
-               new_vec = tokenize(str, 0, 0, NULL);
-       }
-       if (new_vec == NULL && errno == EBADMSG) /* quotes not closed */
-               return EC_PARSE_NOMATCH;
-       if (new_vec == NULL)
-               goto fail;
-
-       ret = ec_node_parse_child(node->child, state, new_vec);
-       if (ret < 0)
-               goto fail;
-
-       if ((unsigned)ret == ec_strvec_len(new_vec)) {
-               ret = 1;
-       } else if (ret != EC_PARSE_NOMATCH) {
-               child_parse = ec_parse_get_last_child(state);
-               ec_parse_unlink_child(state, child_parse);
-               ec_parse_free(child_parse);
-               ret = EC_PARSE_NOMATCH;
-       }
-
-       ec_strvec_free(new_vec);
-       new_vec = NULL;
-
-       return ret;
-
- fail:
-       ec_strvec_free(new_vec);
-       return -1;
-}
-
-static int
-ec_node_sh_lex_complete(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-       struct ec_comp *tmp_comp = NULL;
-       struct ec_strvec *new_vec = NULL;
-       struct ec_comp_iter *iter = NULL;
-       struct ec_comp_item *item = NULL;
-       char *new_str = NULL;
-       const char *str;
-       char missing_quote = '\0';
-       int ret;
-
-       if (ec_strvec_len(strvec) != 1)
-               return 0;
-
-       str = ec_strvec_val(strvec, 0);
-       new_vec = tokenize(str, 1, 1, &missing_quote);
-       if (new_vec == NULL)
-               goto fail;
-
-       /* we will store the completions in a temporary struct, because
-        * we want to update them (ex: add missing quotes) */
-       tmp_comp = ec_comp(ec_comp_get_state(comp));
-       if (tmp_comp == NULL)
-               goto fail;
-
-       ret = ec_node_complete_child(node->child, tmp_comp, new_vec);
-       if (ret < 0)
-               goto fail;
-
-       /* add missing quote for full completions  */
-       if (missing_quote != '\0') {
-               iter = ec_comp_iter(tmp_comp, EC_COMP_FULL);
-               if (iter == NULL)
-                       goto fail;
-               while ((item = ec_comp_iter_next(iter)) != NULL) {
-                       str = ec_comp_item_get_str(item);
-                       if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str,
-                                       missing_quote) < 0) {
-                               new_str = NULL;
-                               goto fail;
-                       }
-                       if (ec_comp_item_set_str(item, new_str) < 0)
-                               goto fail;
-                       ec_free(new_str);
-                       new_str = NULL;
-
-                       str = ec_comp_item_get_completion(item);
-                       if (ec_asprintf(&new_str, "%s%c", str,
-                                       missing_quote) < 0) {
-                               new_str = NULL;
-                               goto fail;
-                       }
-                       if (ec_comp_item_set_completion(item, new_str) < 0)
-                               goto fail;
-                       ec_free(new_str);
-                       new_str = NULL;
-               }
-       }
-
-       ec_comp_iter_free(iter);
-       ec_strvec_free(new_vec);
-
-       ec_comp_merge(comp, tmp_comp);
-
-       return 0;
-
- fail:
-       ec_comp_free(tmp_comp);
-       ec_comp_iter_free(iter);
-       ec_strvec_free(new_vec);
-       ec_free(new_str);
-
-       return -1;
-}
-
-static void ec_node_sh_lex_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-
-       ec_node_free(node->child);
-}
-
-static size_t
-ec_node_sh_lex_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_sh_lex_get_child(const struct ec_node *gen_node, size_t i,
-       struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *refs = 1;
-       *child = node->child;
-       return 0;
-}
-
-static struct ec_node_type ec_node_sh_lex_type = {
-       .name = "sh_lex",
-       .parse = ec_node_sh_lex_parse,
-       .complete = ec_node_sh_lex_complete,
-       .size = sizeof(struct ec_node_sh_lex),
-       .free_priv = ec_node_sh_lex_free_priv,
-       .get_children_count = ec_node_sh_lex_get_children_count,
-       .get_child = ec_node_sh_lex_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
-
-struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
-{
-       struct ec_node_sh_lex *node = NULL;
-
-       if (child == NULL)
-               return NULL;
-
-       node = (struct ec_node_sh_lex *)ec_node_from_type(&ec_node_sh_lex_type, id);
-       if (node == NULL) {
-               ec_node_free(child);
-               return NULL;
-       }
-
-       node->child = child;
-
-       return &node->gen;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_sh_lex_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_sh_lex(EC_NO_ID,
-               EC_NODE_SEQ(EC_NO_ID,
-                       ec_node_str(EC_NO_ID, "foo"),
-                       ec_node_option(EC_NO_ID,
-                               ec_node_str(EC_NO_ID, "toto")
-                       ),
-                       ec_node_str(EC_NO_ID, "bar")
-               )
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_sh_lex(EC_NO_ID,
-               EC_NODE_SEQ(EC_NO_ID,
-                       ec_node_str(EC_NO_ID, "foo"),
-                       ec_node_option(EC_NO_ID,
-                               ec_node_str(EC_NO_ID, "toto")
-                       ),
-                       ec_node_str(EC_NO_ID, "bar"),
-                       ec_node_str(EC_NO_ID, "titi")
-               )
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               " ", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo ", EC_NODE_ENDLIST,
-               "bar", "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo t", EC_NODE_ENDLIST,
-               "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo b", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo bar", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo bar ", EC_NODE_ENDLIST,
-               "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo toto bar ", EC_NODE_ENDLIST,
-               "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo barx", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo 'b", EC_NODE_ENDLIST,
-               "'bar'", EC_NODE_ENDLIST);
-
-       ec_node_free(node);
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_sh_lex_test = {
-       .name = "node_sh_lex",
-       .test = ec_node_sh_lex_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_sh_lex_test);
diff --git a/lib/ecoli_node_sh_lex.h b/lib/ecoli_node_sh_lex.h
deleted file mode 100644 (file)
index d45b998..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_SHLEX_
-#define ECOLI_NODE_SHLEX_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child);
-
-#endif
diff --git a/lib/ecoli_node_space.c b/lib/ecoli_node_space.c
deleted file mode 100644 (file)
index 761ed76..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_malloc.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_space.h>
-
-EC_LOG_TYPE_REGISTER(node_space);
-
-struct ec_node_space {
-       struct ec_node gen;
-};
-
-static int
-ec_node_space_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       const char *str;
-       size_t len = 0;
-
-       (void)state;
-       (void)gen_node;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       str = ec_strvec_val(strvec, 0);
-       while (isspace(str[len]))
-               len++;
-       if (len == 0 || len != strlen(str))
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-static struct ec_node_type ec_node_space_type = {
-       .name = "space",
-       .parse = ec_node_space_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_space),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_space_type);
-
-/* LCOV_EXCL_START */
-static int ec_node_space_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("space", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, " ");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, " ", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo ");
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node("space", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       /* never completes whatever the input */
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               " ", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_space_test = {
-       .name = "space",
-       .test = ec_node_space_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_space_test);
diff --git a/lib/ecoli_node_space.h b/lib/ecoli_node_space.h
deleted file mode 100644 (file)
index 0dd6202..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * This node matches one string in the vector if it is only composed of
- * spaces, as interpreted by isspace().
- */
-
-#ifndef ECOLI_NODE_SPACE_
-#define ECOLI_NODE_SPACE_
-
-/* no API for now, since there is no specific configuration for this node */
-
-#endif
diff --git a/lib/ecoli_node_str.c b/lib/ecoli_node_str.c
deleted file mode 100644 (file)
index d53ea39..0000000
+++ /dev/null
@@ -1,274 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_str.h>
-
-EC_LOG_TYPE_REGISTER(node_str);
-
-struct ec_node_str {
-       struct ec_node gen;
-       char *string;
-       unsigned len;
-};
-
-static int
-ec_node_str_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-       const char *str;
-
-       (void)state;
-
-       if (node->string == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       str = ec_strvec_val(strvec, 0);
-       if (strcmp(str, node->string) != 0)
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-static int
-ec_node_str_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-       const char *str;
-       size_t n = 0;
-
-       if (ec_strvec_len(strvec) != 1)
-               return 0;
-
-       str = ec_strvec_val(strvec, 0);
-       for (n = 0; n < node->len; n++) {
-               if (str[n] != node->string[n])
-                       break;
-       }
-
-       /* no completion */
-       if (str[n] != '\0')
-               return EC_PARSE_NOMATCH;
-
-       if (ec_comp_add_item(comp, gen_node, NULL, EC_COMP_FULL,
-                                       str, node->string) < 0)
-               return -1;
-
-       return 0;
-}
-
-static const char *ec_node_str_desc(const struct ec_node *gen_node)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-
-       return node->string;
-}
-
-static void ec_node_str_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-
-       ec_free(node->string);
-}
-
-static const struct ec_config_schema ec_node_str_schema[] = {
-       {
-               .key = "string",
-               .desc = "The string to match.",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_str_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-       const struct ec_config *value = NULL;
-       char *s = NULL;
-
-       value = ec_config_dict_get(config, "string");
-       if (value == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       s = ec_strdup(value->string);
-       if (s == NULL)
-               goto fail;
-
-       ec_free(node->string);
-       node->string = s;
-       node->len = strlen(node->string);
-
-       return 0;
-
-fail:
-       ec_free(s);
-       return -1;
-}
-
-static struct ec_node_type ec_node_str_type = {
-       .name = "str",
-       .schema = ec_node_str_schema,
-       .set_config = ec_node_str_set_config,
-       .parse = ec_node_str_parse,
-       .complete = ec_node_str_complete,
-       .desc = ec_node_str_desc,
-       .size = sizeof(struct ec_node_str),
-       .free_priv = ec_node_str_free_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_str_type);
-
-int ec_node_str_set_str(struct ec_node *gen_node, const char *str)
-{
-       struct ec_config *config = NULL;
-       int ret;
-
-       if (ec_node_check_type(gen_node, &ec_node_str_type) < 0)
-               goto fail;
-
-       if (str == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "string", ec_config_string(str));
-       if (ret < 0)
-               goto fail;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL;
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       return -1;
-}
-
-struct ec_node *ec_node_str(const char *id, const char *str)
-{
-       struct ec_node *gen_node = NULL;
-
-       gen_node = ec_node_from_type(&ec_node_str_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       if (ec_node_str_set_str(gen_node, str) < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_str_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_str(EC_NO_ID, "foo");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK(!strcmp(ec_node_desc(node), "foo"),
-               "Invalid node description.");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       ec_node_free(node);
-
-       node = ec_node_str(EC_NO_ID, "Здравствуйте");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте",
-               "John!");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       ec_node_free(node);
-
-       /* an empty string node always matches */
-       node = ec_node_str(EC_NO_ID, "");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_str(EC_NO_ID, "foo");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_str_test = {
-       .name = "node_str",
-       .test = ec_node_str_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_str_test);
diff --git a/lib/ecoli_node_str.h b/lib/ecoli_node_str.h
deleted file mode 100644 (file)
index 8a8634f..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_STR_
-#define ECOLI_NODE_STR_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_str(const char *id, const char *str);
-
-/* str is duplicated */
-int ec_node_str_set_str(struct ec_node *node, const char *str);
-
-#endif
diff --git a/lib/ecoli_node_subset.c b/lib/ecoli_node_subset.c
deleted file mode 100644 (file)
index e3184ef..0000000
+++ /dev/null
@@ -1,430 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <stdbool.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_subset.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_or.h>
-#include <ecoli_test.h>
-
-EC_LOG_TYPE_REGISTER(node_subset);
-
-struct ec_node_subset {
-       struct ec_node gen;
-       struct ec_node **table;
-       unsigned int len;
-};
-
-struct parse_result {
-       size_t parse_len;           /* number of parsed nodes */
-       size_t len;                 /* consumed strings */
-};
-
-/* recursively find the longest list of nodes that matches: the state is
- * updated accordingly. */
-static int
-__ec_node_subset_parse(struct parse_result *out, struct ec_node **table,
-               size_t table_len, struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node **child_table;
-       struct ec_strvec *childvec = NULL;
-       size_t i, j, len = 0;
-       struct parse_result best_result, result;
-       struct ec_parse *best_parse = NULL;
-       int ret;
-
-       if (table_len == 0)
-               return 0;
-
-       memset(&best_result, 0, sizeof(best_result));
-
-       child_table = ec_calloc(table_len - 1, sizeof(*child_table));
-       if (child_table == NULL)
-               goto fail;
-
-       for (i = 0; i < table_len; i++) {
-               /* try to parse elt i */
-               ret = ec_node_parse_child(table[i], state, strvec);
-               if (ret < 0)
-                       goto fail;
-
-               if (ret == EC_PARSE_NOMATCH)
-                       continue;
-
-               /* build a new table without elt i */
-               for (j = 0; j < table_len; j++) {
-                       if (j < i)
-                               child_table[j] = table[j];
-                       else if (j > i)
-                               child_table[j - 1] = table[j];
-               }
-
-               /* build a new strvec (ret is the len of matched strvec) */
-               len = ret;
-               childvec = ec_strvec_ndup(strvec, len,
-                                       ec_strvec_len(strvec) - len);
-               if (childvec == NULL)
-                       goto fail;
-
-               memset(&result, 0, sizeof(result));
-               ret = __ec_node_subset_parse(&result, child_table,
-                                       table_len - 1, state, childvec);
-               ec_strvec_free(childvec);
-               childvec = NULL;
-               if (ret < 0)
-                       goto fail;
-
-               /* if result is not the best, ignore */
-               if (result.parse_len < best_result.parse_len) {
-                       memset(&result, 0, sizeof(result));
-                       ec_parse_del_last_child(state);
-                       continue;
-               }
-
-               /* replace the previous best result */
-               ec_parse_free(best_parse);
-               best_parse = ec_parse_get_last_child(state);
-               ec_parse_unlink_child(state, best_parse);
-
-               best_result.parse_len = result.parse_len + 1;
-               best_result.len = len + result.len;
-
-               memset(&result, 0, sizeof(result));
-       }
-
-       *out = best_result;
-       ec_free(child_table);
-       if (best_parse != NULL)
-               ec_parse_link_child(state, best_parse);
-
-       return 0;
-
- fail:
-       ec_parse_free(best_parse);
-       ec_strvec_free(childvec);
-       ec_free(child_table);
-       return -1;
-}
-
-static int
-ec_node_subset_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-       struct ec_parse *parse = NULL;
-       struct parse_result result;
-       int ret;
-
-       memset(&result, 0, sizeof(result));
-
-       ret = __ec_node_subset_parse(&result, node->table,
-                               node->len, state, strvec);
-       if (ret < 0)
-               goto fail;
-
-       /* if no child node matches, return a matching empty strvec */
-       if (result.parse_len == 0)
-               return 0;
-
-       return result.len;
-
- fail:
-       ec_parse_free(parse);
-       return ret;
-}
-
-static int
-__ec_node_subset_complete(struct ec_node **table, size_t table_len,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_parse *parse = ec_comp_get_state(comp);
-       struct ec_strvec *childvec = NULL;
-       struct ec_node *save;
-       size_t i, len;
-       int ret;
-
-       /*
-        * example with table = [a, b, c]
-        * subset_complete([a,b,c], strvec) returns:
-        *   complete(a, strvec) + complete(b, strvec) + complete(c, strvec) +
-        *   + __subset_complete([b, c], childvec) if a matches
-        *   + __subset_complete([a, c], childvec) if b matches
-        *   + __subset_complete([a, b], childvec) if c matches
-        */
-
-       /* first, try to complete with each node of the table */
-       for (i = 0; i < table_len; i++) {
-               if (table[i] == NULL)
-                       continue;
-
-               ret = ec_node_complete_child(table[i],
-                                       comp, strvec);
-               if (ret < 0)
-                       goto fail;
-       }
-
-       /* then, if a node matches, advance in strvec and try to complete with
-        * all the other nodes */
-       for (i = 0; i < table_len; i++) {
-               if (table[i] == NULL)
-                       continue;
-
-               ret = ec_node_parse_child(table[i], parse, strvec);
-               if (ret < 0)
-                       goto fail;
-
-               if (ret == EC_PARSE_NOMATCH)
-                       continue;
-
-               len = ret;
-               childvec = ec_strvec_ndup(strvec, len,
-                                       ec_strvec_len(strvec) - len);
-               if (childvec == NULL) {
-                       ec_parse_del_last_child(parse);
-                       goto fail;
-               }
-
-               save = table[i];
-               table[i] = NULL;
-               ret = __ec_node_subset_complete(table, table_len,
-                                               comp, childvec);
-               table[i] = save;
-               ec_strvec_free(childvec);
-               childvec = NULL;
-               ec_parse_del_last_child(parse);
-
-               if (ret < 0)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       return -1;
-}
-
-static int
-ec_node_subset_complete(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-
-       return __ec_node_subset_complete(node->table, node->len, comp,
-                                       strvec);
-}
-
-static void ec_node_subset_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-       size_t i;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-}
-
-static size_t
-ec_node_subset_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-       return node->len;
-}
-
-static int
-ec_node_subset_get_child(const struct ec_node *gen_node, size_t i,
-                       struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-
-       if (i >= node->len)
-               return -1;
-
-       *child = node->table[i];
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_subset_type = {
-       .name = "subset",
-       .parse = ec_node_subset_parse,
-       .complete = ec_node_subset_complete,
-       .size = sizeof(struct ec_node_subset),
-       .free_priv = ec_node_subset_free_priv,
-       .get_children_count = ec_node_subset_get_children_count,
-       .get_child = ec_node_subset_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_subset_type);
-
-int ec_node_subset_add(struct ec_node *gen_node, struct ec_node *child)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-       struct ec_node **table;
-
-       assert(node != NULL); // XXX specific assert for it, like in libyang
-
-       if (child == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (ec_node_check_type(gen_node, &ec_node_subset_type) < 0)
-               goto fail;
-
-       table = ec_realloc(node->table, (node->len + 1) * sizeof(*node->table));
-       if (table == NULL) {
-               ec_node_free(child);
-               return -1;
-       }
-
-       node->table = table;
-       table[node->len] = child;
-       node->len++;
-
-       return 0;
-
-fail:
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *__ec_node_subset(const char *id, ...)
-{
-       struct ec_node *gen_node = NULL;
-       struct ec_node_subset *node = NULL;
-       struct ec_node *child;
-       va_list ap;
-       int fail = 0;
-
-       va_start(ap, id);
-
-       gen_node = ec_node_from_type(&ec_node_subset_type, id);
-       node = (struct ec_node_subset *)gen_node;
-       if (node == NULL)
-               fail = 1;;
-
-       for (child = va_arg(ap, struct ec_node *);
-            child != EC_NODE_ENDLIST;
-            child = va_arg(ap, struct ec_node *)) {
-
-               /* on error, don't quit the loop to avoid leaks */
-               if (fail == 1 || child == NULL ||
-                               ec_node_subset_add(gen_node, child) < 0) {
-                       fail = 1;
-                       ec_node_free(child);
-               }
-       }
-
-       if (fail == 1)
-               goto fail;
-
-       va_end(ap);
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node); /* will also free children */
-       va_end(ap);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_subset_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = EC_NODE_SUBSET(EC_NO_ID,
-               EC_NODE_OR(EC_NO_ID,
-                       ec_node_str(EC_NO_ID, "foo"),
-                       ec_node_str(EC_NO_ID, "bar")),
-               ec_node_str(EC_NO_ID, "bar"),
-               ec_node_str(EC_NO_ID, "toto")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "titi");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "toto");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, " ");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "foox");
-       ec_node_free(node);
-
-       /* test completion */
-       node = EC_NODE_SUBSET(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_str(EC_NO_ID, "bar"),
-               ec_node_str(EC_NO_ID, "bar2"),
-               ec_node_str(EC_NO_ID, "toto"),
-               ec_node_str(EC_NO_ID, "titi")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "bar2", "bar", "foo", "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", "bar2", "", EC_NODE_ENDLIST,
-               "foo", "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "b", EC_NODE_ENDLIST,
-               "bar", "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", EC_NODE_ENDLIST,
-               "bar", "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", "b", EC_NODE_ENDLIST,
-               "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "t", EC_NODE_ENDLIST,
-               "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "to", EC_NODE_ENDLIST,
-               "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_subset_test = {
-       .name = "node_subset",
-       .test = ec_node_subset_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_subset_test);
diff --git a/lib/ecoli_node_subset.h b/lib/ecoli_node_subset.h
deleted file mode 100644 (file)
index 734b1ae..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_SUBSET_
-#define ECOLI_NODE_SUBSET_
-
-#include <ecoli_node.h>
-
-#define EC_NODE_SUBSET(args...) __ec_node_subset(args, EC_NODE_ENDLIST)
-
-/* list must be terminated with EC_NODE_ENDLIST */
-/* all nodes given in the list will be freed when freeing this one */
-/* avoid using this function directly, prefer the macro EC_NODE_SUBSET() or
- * ec_node_subset() + ec_node_subset_add() */
-struct ec_node *__ec_node_subset(const char *id, ...);
-
-struct ec_node *ec_node_subset(const char *id);
-
-/* child is consumed */
-int ec_node_subset_add(struct ec_node *node, struct ec_node *child);
-
-#endif
diff --git a/lib/ecoli_parse.c b/lib/ecoli_parse.c
deleted file mode 100644 (file)
index 6396fc1..0000000
+++ /dev/null
@@ -1,540 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <ecoli_assert.h>
-#include <ecoli_malloc.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_node.h>
-#include <ecoli_node_sh_lex.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_parse.h>
-
-EC_LOG_TYPE_REGISTER(parse);
-
-TAILQ_HEAD(ec_parse_list, ec_parse);
-
-struct ec_parse {
-       TAILQ_ENTRY(ec_parse) next;
-       struct ec_parse_list children;
-       struct ec_parse *parent;
-       const struct ec_node *node;
-       struct ec_strvec *strvec;
-       struct ec_keyval *attrs;
-};
-
-static int __ec_node_parse_child(const struct ec_node *node,
-                               struct ec_parse *state,
-                               bool is_root, const struct ec_strvec *strvec)
-{
-       struct ec_strvec *match_strvec;
-       struct ec_parse *child = NULL;
-       int ret;
-
-       if (ec_node_type(node)->parse == NULL) {
-               errno = ENOTSUP;
-               return -1;
-       }
-
-       if (!is_root) {
-               child = ec_parse(node);
-               if (child == NULL)
-                       return -1;
-
-               ec_parse_link_child(state, child);
-       } else {
-               child = state;
-       }
-       ret = ec_node_type(node)->parse(node, child, strvec);
-       if (ret < 0)
-               goto fail;
-
-       if (ret == EC_PARSE_NOMATCH) {
-               if (!is_root) {
-                       ec_parse_unlink_child(state, child);
-                       ec_parse_free(child);
-               }
-               return ret;
-       }
-
-       match_strvec = ec_strvec_ndup(strvec, 0, ret);
-       if (match_strvec == NULL)
-               goto fail;
-
-       child->strvec = match_strvec;
-
-       return ret;
-
-fail:
-       if (!is_root) {
-               ec_parse_unlink_child(state, child);
-               ec_parse_free(child);
-       }
-       return -1;
-}
-
-int ec_node_parse_child(const struct ec_node *node, struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       assert(state != NULL);
-       return __ec_node_parse_child(node, state, false, strvec);
-}
-
-struct ec_parse *ec_node_parse_strvec(const struct ec_node *node,
-                               const struct ec_strvec *strvec)
-{
-       struct ec_parse *parse = ec_parse(node);
-       int ret;
-
-       if (parse == NULL)
-               return NULL;
-
-       ret = __ec_node_parse_child(node, parse, true, strvec);
-       if (ret < 0) {
-               ec_parse_free(parse);
-               return NULL;
-       }
-
-       return parse;
-}
-
-struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str)
-{
-       struct ec_strvec *strvec = NULL;
-       struct ec_parse *parse = NULL;
-
-       errno = ENOMEM;
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       if (ec_strvec_add(strvec, str) < 0)
-               goto fail;
-
-       parse = ec_node_parse_strvec(node, strvec);
-       if (parse == NULL)
-               goto fail;
-
-       ec_strvec_free(strvec);
-       return parse;
-
- fail:
-       ec_strvec_free(strvec);
-       ec_parse_free(parse);
-       return NULL;
-}
-
-struct ec_parse *ec_parse(const struct ec_node *node)
-{
-       struct ec_parse *parse = NULL;
-
-       parse = ec_calloc(1, sizeof(*parse));
-       if (parse == NULL)
-               goto fail;
-
-       TAILQ_INIT(&parse->children);
-
-       parse->node = node;
-       parse->attrs = ec_keyval();
-       if (parse->attrs == NULL)
-               goto fail;
-
-       return parse;
-
- fail:
-       if (parse != NULL)
-               ec_keyval_free(parse->attrs);
-       ec_free(parse);
-
-       return NULL;
-}
-
-static struct ec_parse *
-__ec_parse_dup(const struct ec_parse *root, const struct ec_parse *ref,
-               struct ec_parse **new_ref)
-{
-       struct ec_parse *dup = NULL;
-       struct ec_parse *child, *dup_child;
-       struct ec_keyval *attrs = NULL;
-
-       if (root == NULL)
-               return NULL;
-
-       dup = ec_parse(root->node);
-       if (dup == NULL)
-               return NULL;
-
-       if (root == ref)
-               *new_ref = dup;
-
-       attrs = ec_keyval_dup(root->attrs);
-       if (attrs == NULL)
-               goto fail;
-       ec_keyval_free(dup->attrs);
-       dup->attrs = attrs;
-
-       if (root->strvec != NULL) {
-               dup->strvec = ec_strvec_dup(root->strvec);
-               if (dup->strvec == NULL)
-                       goto fail;
-       }
-
-       TAILQ_FOREACH(child, &root->children, next) {
-               dup_child = __ec_parse_dup(child, ref, new_ref);
-               if (dup_child == NULL)
-                       goto fail;
-               ec_parse_link_child(dup, dup_child);
-       }
-
-       return dup;
-
-fail:
-       ec_parse_free(dup);
-       return NULL;
-}
-
-struct ec_parse *ec_parse_dup(const struct ec_parse *parse)
-{
-       const struct ec_parse *root;
-       struct ec_parse *dup_root, *dup = NULL;
-
-       root = ec_parse_get_root(parse);
-       dup_root = __ec_parse_dup(root, parse, &dup);
-       if (dup_root == NULL)
-               return NULL;
-       assert(dup != NULL);
-
-       return dup;
-}
-
-void ec_parse_free_children(struct ec_parse *parse)
-{
-       struct ec_parse *child;
-
-       if (parse == NULL)
-               return;
-
-       while (!TAILQ_EMPTY(&parse->children)) {
-               child = TAILQ_FIRST(&parse->children);
-               TAILQ_REMOVE(&parse->children, child, next);
-               child->parent = NULL;
-               ec_parse_free(child);
-       }
-}
-
-void ec_parse_free(struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return;
-
-       ec_assert_print(parse->parent == NULL,
-                       "parent not NULL in ec_parse_free()");
-
-       ec_parse_free_children(parse);
-       ec_strvec_free(parse->strvec);
-       ec_keyval_free(parse->attrs);
-       ec_free(parse);
-}
-
-static void __ec_parse_dump(FILE *out,
-       const struct ec_parse *parse, size_t indent)
-{
-       struct ec_parse *child;
-       const struct ec_strvec *vec;
-       const char *id = "none", *typename = "none";
-
-       /* node can be null when parsing is incomplete */
-       if (parse->node != NULL) {
-               id = parse->node->id;
-               typename = ec_node_type(parse->node)->name;
-       }
-
-       fprintf(out, "%*s" "type=%s id=%s vec=",
-               (int)indent * 4, "", typename, id);
-       vec = ec_parse_strvec(parse);
-       ec_strvec_dump(out, vec);
-
-       TAILQ_FOREACH(child, &parse->children, next)
-               __ec_parse_dump(out, child, indent + 1);
-}
-
-void ec_parse_dump(FILE *out, const struct ec_parse *parse)
-{
-       fprintf(out, "------------------- parse dump:\n");
-
-       if (parse == NULL) {
-               fprintf(out, "parse is NULL\n");
-               return;
-       }
-
-       /* only exist if it does not match (strvec == NULL) and if it
-        * does not have children: an incomplete parse, like those
-        * generated by complete() don't match but have children that
-        * may match. */
-       if (!ec_parse_matches(parse) && TAILQ_EMPTY(&parse->children)) {
-               fprintf(out, "no match\n");
-               return;
-       }
-
-       __ec_parse_dump(out, parse, 0);
-}
-
-void ec_parse_link_child(struct ec_parse *parse,
-       struct ec_parse *child)
-{
-       TAILQ_INSERT_TAIL(&parse->children, child, next);
-       child->parent = parse;
-}
-
-void ec_parse_unlink_child(struct ec_parse *parse,
-       struct ec_parse *child)
-{
-       TAILQ_REMOVE(&parse->children, child, next);
-       child->parent = NULL;
-}
-
-struct ec_parse *
-ec_parse_get_first_child(const struct ec_parse *parse)
-{
-       return TAILQ_FIRST(&parse->children);
-}
-
-struct ec_parse *
-ec_parse_get_last_child(const struct ec_parse *parse)
-{
-       return TAILQ_LAST(&parse->children, ec_parse_list);
-}
-
-struct ec_parse *ec_parse_get_next(const struct ec_parse *parse)
-{
-       return TAILQ_NEXT(parse, next);
-}
-
-bool ec_parse_has_child(const struct ec_parse *parse)
-{
-       return !TAILQ_EMPTY(&parse->children);
-}
-
-const struct ec_node *ec_parse_get_node(const struct ec_parse *parse)
-{
-       return parse->node;
-}
-
-void ec_parse_del_last_child(struct ec_parse *parse)
-{
-       struct ec_parse *child;
-
-       child = ec_parse_get_last_child(parse);
-       ec_parse_unlink_child(parse, child);
-       ec_parse_free(child);
-}
-
-struct ec_parse *__ec_parse_get_root(struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return NULL;
-
-       while (parse->parent != NULL)
-               parse = parse->parent;
-
-       return parse;
-}
-
-struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return NULL;
-
-       return parse->parent;
-}
-
-struct ec_parse *ec_parse_iter_next(struct ec_parse *parse)
-{
-       struct ec_parse *child, *parent, *next;
-
-       child = TAILQ_FIRST(&parse->children);
-       if (child != NULL)
-               return child;
-       parent = parse->parent;
-       while (parent != NULL) {
-               next = TAILQ_NEXT(parse, next);
-               if (next != NULL)
-                       return next;
-               parse = parent;
-               parent = parse->parent;
-       }
-       return NULL;
-}
-
-struct ec_parse *ec_parse_find_first(struct ec_parse *parse,
-       const char *id)
-{
-       struct ec_parse *iter;
-
-       if (parse == NULL)
-               return NULL;
-
-       for (iter = parse; iter != NULL; iter = ec_parse_iter_next(iter)) {
-               if (iter->node != NULL &&
-                               iter->node->id != NULL &&
-                               !strcmp(iter->node->id, id))
-                       return iter;
-       }
-
-       return NULL;
-}
-
-struct ec_keyval *
-ec_parse_get_attrs(struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return NULL;
-
-       return parse->attrs;
-}
-
-const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse)
-{
-       if (parse == NULL || parse->strvec == NULL)
-               return NULL;
-
-       return parse->strvec;
-}
-
-/* number of strings in the parse vector */
-size_t ec_parse_len(const struct ec_parse *parse)
-{
-       if (parse == NULL || parse->strvec == NULL)
-               return 0;
-
-       return ec_strvec_len(parse->strvec);
-}
-
-size_t ec_parse_matches(const struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return 0;
-
-       if (parse->strvec == NULL)
-               return 0;
-
-       return 1;
-}
-
-/* LCOV_EXCL_START */
-static int ec_parse_testcase(void)
-{
-       struct ec_node *node = NULL;
-       struct ec_parse *p = NULL, *p2 = NULL;
-       const struct ec_parse *pc;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-       int testres = 0;
-       int ret;
-
-       node = ec_node_sh_lex(EC_NO_ID,
-                       EC_NODE_SEQ(EC_NO_ID,
-                               ec_node_str("id_x", "x"),
-                               ec_node_str("id_y", "y")));
-       if (node == NULL)
-               goto fail;
-
-       p = ec_node_parse(node, "xcdscds");
-       testres |= EC_TEST_CHECK(
-               p != NULL && !ec_parse_matches(p),
-               "parse should not match\n");
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_parse_dump(f, p);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "no match"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-       ec_parse_free(p);
-
-       p = ec_node_parse(node, "x y");
-       testres |= EC_TEST_CHECK(
-               p != NULL && ec_parse_matches(p),
-               "parse should match\n");
-       testres |= EC_TEST_CHECK(
-               ec_parse_len(p) == 1, "bad parse len\n");
-
-       ret = ec_keyval_set(ec_parse_get_attrs(p), "key", "val", NULL);
-       testres |= EC_TEST_CHECK(ret == 0,
-               "cannot set parse attribute\n");
-
-       p2 = ec_parse_dup(p);
-       testres |= EC_TEST_CHECK(
-               p2 != NULL && ec_parse_matches(p2),
-               "parse should match\n");
-       ec_parse_free(p2);
-       p2 = NULL;
-
-       pc = ec_parse_find_first(p, "id_x");
-       testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_x");
-       testres |= EC_TEST_CHECK(pc != NULL &&
-               ec_parse_get_parent(pc) != NULL &&
-               ec_parse_get_parent(ec_parse_get_parent(pc)) == p,
-               "invalid parent\n");
-
-       pc = ec_parse_find_first(p, "id_y");
-       testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_y");
-       pc = ec_parse_find_first(p, "id_dezdezdez");
-       testres |= EC_TEST_CHECK(pc == NULL, "should not find bad id");
-
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_parse_dump(f, p);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "type=sh_lex id=no-id") &&
-               strstr(buf, "type=seq id=no-id") &&
-               strstr(buf, "type=str id=id_x") &&
-               strstr(buf, "type=str id=id_x"),
-               "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       ec_parse_free(p);
-       ec_node_free(node);
-       return testres;
-
-fail:
-       ec_parse_free(p2);
-       ec_parse_free(p);
-       ec_node_free(node);
-       if (f != NULL)
-               fclose(f);
-       free(buf);
-
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_parse_test = {
-       .name = "parse",
-       .test = ec_parse_testcase,
-};
-
-EC_TEST_REGISTER(ec_parse_test);
diff --git a/lib/ecoli_parse.h b/lib/ecoli_parse.h
deleted file mode 100644 (file)
index 79e644f..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Node parse API.
- *
- * The parse operation is to check if an input (a string or vector of
- * strings) matches the node tree. On success, the result is stored in a
- * tree that describes which part of the input matches which node.
- */
-
-#ifndef ECOLI_PARSE_
-#define ECOLI_PARSE_
-
-#include <sys/queue.h>
-#include <sys/types.h>
-#include <limits.h>
-#include <stdio.h>
-
-struct ec_node;
-struct ec_parse;
-
-/**
- * Create an empty parse tree.
- *
- * @return
- *   The empty parse tree.
- */
-struct ec_parse *ec_parse(const struct ec_node *node);
-
-/**
- *
- *
- *
- */
-void ec_parse_free(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-void ec_parse_free_children(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_dup(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse);
-
-/* a NULL return value is an error, with errno set
-  ENOTSUP: no ->parse() operation
-*/
-/**
- *
- *
- *
- */
-struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_node_parse_strvec(const struct ec_node *node,
-                               const struct ec_strvec *strvec);
-
-/**
- *
- *
- *
- */
-#define EC_PARSE_NOMATCH INT_MAX
-
-/* internal: used by nodes
- *
- * state is the current parse tree, which is built piece by piece while
- *   parsing the node tree: ec_node_parse_child() creates a new child in
- *   this state parse tree, and calls the parse() method for the child
- *   node, with state pointing to this new child. If it does not match,
- *   the child is removed in the state, else it is kept, with its
- *   possible descendants.
- *
- * return:
- * the number of matched strings in strvec on success
- * EC_PARSE_NOMATCH (positive) if it does not match
- * -1 on error, and errno is set
- */
-int ec_node_parse_child(const struct ec_node *node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec);
-
-/**
- *
- *
- *
- */
-void ec_parse_link_child(struct ec_parse *parse,
-                       struct ec_parse *child);
-/**
- *
- *
- *
- */
-void ec_parse_unlink_child(struct ec_parse *parse,
-                       struct ec_parse *child);
-
-/* keep the const */
-#define ec_parse_get_root(parse) ({                            \
-       const struct ec_parse *p_ = parse; /* check type */     \
-       struct ec_parse *parse_ = (struct ec_parse *)parse;     \
-       typeof(parse) res_;                                     \
-       (void)p_;                                               \
-       res_ = __ec_parse_get_root(parse_);                     \
-       res_;                                                   \
-})
-
-/**
- *
- *
- *
- */
-struct ec_parse *__ec_parse_get_root(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse);
-
-/**
- * Get the first child of a tree.
- *
- */
-struct ec_parse *ec_parse_get_first_child(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_get_last_child(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_get_next(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-#define EC_PARSE_FOREACH_CHILD(child, parse)                   \
-       for (child = ec_parse_get_first_child(parse);           \
-               child != NULL;                                  \
-               child = ec_parse_get_next(child))               \
-
-/**
- *
- *
- *
- */
-bool ec_parse_has_child(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-const struct ec_node *ec_parse_get_node(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-void ec_parse_del_last_child(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_keyval *ec_parse_get_attrs(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-void ec_parse_dump(FILE *out, const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_find_first(struct ec_parse *parse,
-       const char *id);
-
-/**
- * Iterate among parse tree
- *
- * Use it with:
- * for (iter = state; iter != NULL; iter = ec_parse_iter_next(iter))
- */
-struct ec_parse *ec_parse_iter_next(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-size_t ec_parse_len(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-size_t ec_parse_matches(const struct ec_parse *parse);
-
-#endif
diff --git a/lib/ecoli_string.c b/lib/ecoli_string.c
deleted file mode 100644 (file)
index 7723818..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdarg.h>
-#include <stddef.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <ecoli_assert.h>
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-
-/* count the number of identical chars at the beginning of 2 strings */
-size_t ec_strcmp_count(const char *s1, const char *s2)
-{
-       size_t i = 0;
-
-       while (s1[i] && s2[i] && s1[i] == s2[i])
-               i++;
-
-       return i;
-}
-
-int ec_str_startswith(const char *s, const char *beginning)
-{
-       size_t len;
-
-       len = ec_strcmp_count(s, beginning);
-       if (beginning[len] == '\0')
-               return 1;
-
-       return 0;
-}
-
-int ec_vasprintf(char **buf, const char *fmt, va_list ap)
-{
-       char dummy;
-       int buflen, ret;
-       va_list aq;
-
-       va_copy(aq, ap);
-       *buf = NULL;
-       ret = vsnprintf(&dummy, 1, fmt, aq);
-       va_end(aq);
-       if (ret < 0)
-               return ret;
-
-       buflen = ret + 1;
-       *buf = ec_malloc(buflen);
-       if (*buf == NULL)
-               return -1;
-
-       va_copy(aq, ap);
-       ret = vsnprintf(*buf, buflen, fmt, aq);
-       va_end(aq);
-
-       ec_assert_print(ret < buflen, "invalid return value for vsnprintf");
-       if (ret < 0) {
-               free(*buf);
-               *buf = NULL;
-               return -1;
-       }
-
-       return ret;
-}
-
-int ec_asprintf(char **buf, const char *fmt, ...)
-{
-       va_list ap;
-       int ret;
-
-       va_start(ap, fmt);
-       ret = ec_vasprintf(buf, fmt, ap);
-       va_end(ap);
-
-       return ret;
-}
diff --git a/lib/ecoli_string.h b/lib/ecoli_string.h
deleted file mode 100644 (file)
index add73a0..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_STRING_
-#define ECOLI_STRING_
-
-#include <stddef.h>
-
-/* count the number of identical chars at the beginning of 2 strings */
-size_t ec_strcmp_count(const char *s1, const char *s2);
-
-/* return 1 if 's' starts with 'beginning' */
-int ec_str_startswith(const char *s, const char *beginning);
-
-/* like asprintf, but use libecoli allocator */
-int ec_asprintf(char **buf, const char *fmt, ...);
-
-/* like vasprintf, but use libecoli allocator */
-int ec_vasprintf(char **buf, const char *fmt, va_list ap);
-
-#endif
diff --git a/lib/ecoli_strvec.c b/lib/ecoli_strvec.c
deleted file mode 100644 (file)
index c574b1b..0000000
+++ /dev/null
@@ -1,427 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#define _GNU_SOURCE /* qsort_r */
-#include <sys/types.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_log.h>
-#include <ecoli_strvec.h>
-
-EC_LOG_TYPE_REGISTER(strvec);
-
-struct ec_strvec_elt {
-       unsigned int refcnt;
-       char *str;
-};
-
-struct ec_strvec {
-       size_t len;
-       struct ec_strvec_elt **vec;
-};
-
-struct ec_strvec *ec_strvec(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)
-{
-       struct ec_strvec_elt *elt, **new_vec;
-
-       new_vec = ec_realloc(strvec->vec,
-               sizeof(*strvec->vec) * (strvec->len + 1));
-       if (new_vec == NULL)
-               return -1;
-
-       strvec->vec = new_vec;
-
-       elt = ec_malloc(sizeof(*elt));
-       if (elt == NULL)
-               return -1;
-
-       elt->str = ec_strdup(s);
-       if (elt->str == NULL) {
-               ec_free(elt);
-               return -1;
-       }
-       elt->refcnt = 1;
-
-       new_vec[strvec->len] = elt;
-       strvec->len++;
-       return 0;
-}
-
-struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
-       size_t n)
-{
-       struct ec_strvec *strvec = NULL;
-       size_t i;
-
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       for (i = 0; i < n; i++) {
-               if (ec_strvec_add(strvec, strarr[i]) < 0)
-                       goto fail;
-       }
-
-       return strvec;
-
-fail:
-       ec_strvec_free(strvec);
-       return NULL;
-}
-
-int ec_strvec_del_last(struct ec_strvec *strvec)
-{
-       struct ec_strvec_elt *elt;
-
-       if (strvec->len == 0) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       elt = strvec->vec[strvec->len - 1];
-       elt->refcnt--;
-       if (elt->refcnt == 0) {
-               ec_free(elt->str);
-               ec_free(elt);
-       }
-       strvec->len--;
-       return 0;
-}
-
-struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t off,
-       size_t len)
-{
-       struct ec_strvec *copy = NULL;
-       size_t i, veclen;
-
-       veclen = ec_strvec_len(strvec);
-       if (off + len > veclen)
-               return NULL;
-
-       copy = ec_strvec();
-       if (copy == NULL)
-               goto fail;
-
-       if (len == 0)
-               return copy;
-
-       copy->vec = ec_calloc(len, sizeof(*copy->vec));
-       if (copy->vec == NULL)
-               goto fail;
-
-       for (i = 0; i < len; i++) {
-               copy->vec[i] = strvec->vec[i + off];
-               copy->vec[i]->refcnt++;
-       }
-       copy->len = 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, 0, ec_strvec_len(strvec));
-}
-
-void ec_strvec_free(struct ec_strvec *strvec)
-{
-       struct ec_strvec_elt *elt;
-       size_t i;
-
-       if (strvec == NULL)
-               return;
-
-       for (i = 0; i < ec_strvec_len(strvec); i++) {
-               elt = strvec->vec[i];
-               elt->refcnt--;
-               if (elt->refcnt == 0) {
-                       ec_free(elt->str);
-                       ec_free(elt);
-               }
-       }
-
-       ec_free(strvec->vec);
-       ec_free(strvec);
-}
-
-size_t ec_strvec_len(const struct ec_strvec *strvec)
-{
-       return strvec->len;
-}
-
-const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx)
-{
-       if (strvec == NULL || idx >= strvec->len)
-               return NULL;
-
-       return strvec->vec[idx]->str;
-}
-
-int ec_strvec_cmp(const struct ec_strvec *strvec1,
-               const struct ec_strvec *strvec2)
-{
-       size_t i;
-
-       if (ec_strvec_len(strvec1) != ec_strvec_len(strvec2))
-               return -1;
-
-       for (i = 0; i < ec_strvec_len(strvec1); i++) {
-               if (strcmp(ec_strvec_val(strvec1, i),
-                               ec_strvec_val(strvec2, i)))
-                       return -1;
-       }
-
-       return 0;
-}
-
-static int
-cmp_vec_elt(const void *p1, const void *p2, void *arg)
-{
-       int (*str_cmp)(const char *s1, const char *s2) = arg;
-       const struct ec_strvec_elt * const *e1 = p1, * const *e2 = p2;
-
-       return str_cmp((*e1)->str, (*e2)->str);
-}
-
-void ec_strvec_sort(struct ec_strvec *strvec,
-       int (*str_cmp)(const char *s1, const char *s2))
-{
-       if (str_cmp == NULL)
-               str_cmp = strcmp;
-       qsort_r(strvec->vec, ec_strvec_len(strvec),
-               sizeof(*strvec->vec), cmp_vec_elt, str_cmp);
-}
-
-void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec)
-{
-       size_t i;
-
-       if (strvec == NULL) {
-               fprintf(out, "none\n");
-               return;
-       }
-
-       fprintf(out, "strvec (len=%zu) [", strvec->len);
-       for (i = 0; i < ec_strvec_len(strvec); i++) {
-               if (i == 0)
-                       fprintf(out, "%s", strvec->vec[i]->str);
-               else
-                       fprintf(out, ", %s", strvec->vec[i]->str);
-       }
-       fprintf(out, "]\n");
-
-}
-
-/* LCOV_EXCL_START */
-static int ec_strvec_testcase(void)
-{
-       struct ec_strvec *strvec = NULL;
-       struct ec_strvec *strvec2 = NULL;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-       int testres = 0;
-
-       strvec = ec_strvec();
-       if (strvec == NULL) {
-               EC_TEST_ERR("cannot create strvec\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec) != 0) {
-               EC_TEST_ERR("bad strvec len (0)\n");
-               goto fail;
-       }
-       if (ec_strvec_add(strvec, "0") < 0) {
-               EC_TEST_ERR("cannot add (0) in strvec\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec) != 1) {
-               EC_TEST_ERR("bad strvec len (1)\n");
-               goto fail;
-       }
-       if (ec_strvec_add(strvec, "1") < 0) {
-               EC_TEST_ERR("cannot add (1) in strvec\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec) != 2) {
-               EC_TEST_ERR("bad strvec len (2)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec, 0), "0")) {
-               EC_TEST_ERR("invalid element in strvec (0)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec, 1), "1")) {
-               EC_TEST_ERR("invalid element in strvec (1)\n");
-               goto fail;
-       }
-       if (ec_strvec_val(strvec, 2) != NULL) {
-               EC_TEST_ERR("strvec val should be NULL\n");
-               goto fail;
-       }
-
-       strvec2 = ec_strvec_dup(strvec);
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec2\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec2) != 2) {
-               EC_TEST_ERR("bad strvec2 len (2)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec2, 0), "0")) {
-               EC_TEST_ERR("invalid element in strvec2 (0)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec2, 1), "1")) {
-               EC_TEST_ERR("invalid element in strvec2 (1)\n");
-               goto fail;
-       }
-       if (ec_strvec_val(strvec2, 2) != NULL) {
-               EC_TEST_ERR("strvec2 val should be NULL\n");
-               goto fail;
-       }
-       ec_strvec_free(strvec2);
-
-       strvec2 = ec_strvec_ndup(strvec, 0, 0);
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec2\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec2) != 0) {
-               EC_TEST_ERR("bad strvec2 len (0)\n");
-               goto fail;
-       }
-       if (ec_strvec_val(strvec2, 0) != NULL) {
-               EC_TEST_ERR("strvec2 val should be NULL\n");
-               goto fail;
-       }
-       ec_strvec_free(strvec2);
-
-       strvec2 = ec_strvec_ndup(strvec, 1, 1);
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec2\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec2) != 1) {
-               EC_TEST_ERR("bad strvec2 len (1)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec2, 0), "1")) {
-               EC_TEST_ERR("invalid element in strvec2 (1)\n");
-               goto fail;
-       }
-       if (ec_strvec_val(strvec2, 1) != NULL) {
-               EC_TEST_ERR("strvec2 val should be NULL\n");
-               goto fail;
-       }
-       ec_strvec_free(strvec2);
-
-       strvec2 = ec_strvec_ndup(strvec, 3, 1);
-       if (strvec2 != NULL) {
-               EC_TEST_ERR("strvec2 should be NULL\n");
-               goto fail;
-       }
-       ec_strvec_free(strvec2);
-
-       strvec2 = EC_STRVEC("0", "1");
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec from array\n");
-               goto fail;
-       }
-       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
-               "strvec and strvec2 should be equal\n");
-       ec_strvec_free(strvec2);
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_strvec_dump(f, strvec);
-       fclose(f);
-       f = NULL;
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "strvec (len=2) [0, 1]"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       ec_strvec_del_last(strvec);
-       strvec2 = EC_STRVEC("0");
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec from array\n");
-               goto fail;
-       }
-       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
-               "strvec and strvec2 should be equal\n");
-       ec_strvec_free(strvec2);
-       strvec2 = NULL;
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_strvec_dump(f, NULL);
-       fclose(f);
-       f = NULL;
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "none"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       ec_strvec_free(strvec);
-
-       strvec = EC_STRVEC("e", "a", "f", "d", "b", "c");
-       if (strvec == NULL) {
-               EC_TEST_ERR("cannot create strvec from array\n");
-               goto fail;
-       }
-       ec_strvec_sort(strvec, NULL);
-       strvec2 = EC_STRVEC("a", "b", "c", "d", "e", "f");
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec from array\n");
-               goto fail;
-       }
-       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
-               "strvec and strvec2 should be equal\n");
-       ec_strvec_free(strvec);
-       strvec = NULL;
-       ec_strvec_free(strvec2);
-       strvec2 = NULL;
-
-       return testres;
-
-fail:
-       if (f != NULL)
-               fclose(f);
-       ec_strvec_free(strvec);
-       ec_strvec_free(strvec2);
-       free(buf);
-
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_str_test = {
-       .name = "strvec",
-       .test = ec_strvec_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_str_test);
diff --git a/lib/ecoli_strvec.h b/lib/ecoli_strvec.h
deleted file mode 100644 (file)
index 8e14973..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Vectors of strings.
- *
- * The ec_strvec API provide helpers to manipulate string vectors.
- * When duplicating vectors, the strings are not duplicated in memory,
- * a reference counter is used.
- */
-
-#ifndef ECOLI_STRVEC_
-#define ECOLI_STRVEC_
-
-#include <stdio.h>
-
-/**
- * Allocate a new empty string vector.
- *
- * @return
- *   The new strvec object, or NULL on error (errno is set).
- */
-struct ec_strvec *ec_strvec(void);
-
-#ifndef EC_COUNT_OF
-#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
-               ((size_t)(!(sizeof(x) % sizeof(0[x])))))
-#endif
-
-/**
- * Allocate a new string vector
- *
- * The string vector is initialized with the list of const strings
- * passed as arguments.
- *
- * @return
- *   The new strvec object, or NULL on error (errno is set).
- */
-#define EC_STRVEC(args...) ({                                          \
-                       const char *_arr[] = {args};                    \
-                       ec_strvec_from_array(_arr, EC_COUNT_OF(_arr));  \
-               })
-/**
- * Allocate a new string vector
- *
- * The string vector is initialized with the array of const strings
- * passed as arguments.
- *
- * @param strarr
- *   The array of const strings.
- * @param n
- *   The number of strings in the array.
- * @return
- *   The new strvec object, or NULL on error (errno is set).
- */
-struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
-       size_t n);
-
-/**
- * Add a string in a vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param s
- *   The string to be added at the end of the vector.
- * @return
- *   0 on success or -1 on error (errno is set).
- */
-int ec_strvec_add(struct ec_strvec *strvec, const char *s);
-
-/**
- * Delete the last entry in the string vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param s
- *   The string to be added at the end of the vector.
- * @return
- *   0 on success or -1 on error (errno is set).
- */
-int ec_strvec_del_last(struct ec_strvec *strvec);
-
-/**
- * Duplicate a string vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @return
- *   The duplicated strvec object, or NULL on error (errno is set).
- */
-struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec);
-
-/**
- * Duplicate a part of a string vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param off
- *   The index of the first string to duplicate.
- * @param
- *   The number of strings to duplicate.
- * @return
- *   The duplicated strvec object, or NULL on error (errno is set).
- */
-struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec,
-       size_t off, size_t len);
-
-/**
- * Free a string vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- */
-void ec_strvec_free(struct ec_strvec *strvec);
-
-/**
- * Get the length of a string vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @return
- *   The length of the vector.
- */
-size_t ec_strvec_len(const struct ec_strvec *strvec);
-
-/**
- * Get a string element from a vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param idx
- *   The index of the string to get.
- * @return
- *   The string stored at given index, or NULL on error (strvec is NULL
- *   or invalid index).
- */
-const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx);
-
-/**
- * Compare two string vectors
- *
- * @param strvec
- *   The pointer to the first string vector.
- * @param strvec
- *   The pointer to the second string vector.
- * @return
- *   0 if the string vectors are equal.
- */
-int ec_strvec_cmp(const struct ec_strvec *strvec1,
-               const struct ec_strvec *strvec2);
-
-/**
- * Sort the string vector.
- *
- * @param strvec
- *   The pointer to the first string vector.
- * @param str_cmp
- *   The sort function to use. If NULL, use strcmp.
- */
-void ec_strvec_sort(struct ec_strvec *strvec,
-               int (*str_cmp)(const char *s1, const char *s2));
-
-/**
- * Dump a string vector.
- *
- * @param out
- *   The stream where the dump is sent.
- * @param strvec
- *   The pointer to the string vector.
- */
-void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec);
-
-#endif
diff --git a/lib/ecoli_test.c b/lib/ecoli_test.c
deleted file mode 100644 (file)
index b090bd3..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_parse.h>
-
-static struct ec_test_list test_list = TAILQ_HEAD_INITIALIZER(test_list);
-
-EC_LOG_TYPE_REGISTER(test);
-
-static struct ec_test *ec_test_lookup(const char *name)
-{
-       struct ec_test *test;
-
-       TAILQ_FOREACH(test, &test_list, next) {
-               if (!strcmp(name, test->name))
-                       return test;
-       }
-
-       errno = EEXIST;
-       return NULL;
-}
-
-int ec_test_register(struct ec_test *test)
-{
-       if (ec_test_lookup(test->name) != NULL)
-               return -1;
-
-       TAILQ_INSERT_TAIL(&test_list, test, next);
-
-       return 0;
-}
-
-int ec_test_check_parse(struct ec_node *tk, int expected, ...)
-{
-       struct ec_parse *p;
-       struct ec_strvec *vec = NULL;
-       const char *s;
-       int ret = -1, match;
-       va_list ap;
-
-       va_start(ap, expected);
-
-       /* build a string vector */
-       vec = ec_strvec();
-       if (vec == NULL)
-               goto out;
-
-       for (s = va_arg(ap, const char *);
-            s != EC_NODE_ENDLIST;
-            s = va_arg(ap, const char *)) {
-               if (s == NULL)
-                       goto out;
-
-               if (ec_strvec_add(vec, s) < 0)
-                       goto out;
-       }
-
-       p = ec_node_parse_strvec(tk, vec);
-       if (p == NULL) {
-               EC_LOG(EC_LOG_ERR, "parse is NULL\n");
-       }
-       if (ec_parse_matches(p))
-               match = ec_parse_len(p);
-       else
-               match = -1;
-       if (expected == match) {
-               ret = 0;
-       } else {
-               EC_LOG(EC_LOG_ERR,
-                       "parse len (%d) does not match expected (%d)\n",
-                       match, expected);
-       }
-
-       ec_parse_free(p);
-
-out:
-       ec_strvec_free(vec);
-       va_end(ap);
-       return ret;
-}
-
-int ec_test_check_complete(struct ec_node *tk, enum ec_comp_type type, ...)
-{
-       struct ec_comp *c = NULL;
-       struct ec_strvec *vec = NULL;
-       const char *s;
-       int ret = 0;
-       unsigned int count = 0;
-       va_list ap;
-
-       va_start(ap, type);
-
-       /* build a string vector */
-       vec = ec_strvec();
-       if (vec == NULL)
-               goto out;
-
-       for (s = va_arg(ap, const char *);
-            s != EC_NODE_ENDLIST;
-            s = va_arg(ap, const char *)) {
-               if (s == NULL)
-                       goto out;
-
-               if (ec_strvec_add(vec, s) < 0)
-                       goto out;
-       }
-
-       c = ec_node_complete_strvec(tk, vec);
-       if (c == NULL) {
-               ret = -1;
-               goto out;
-       }
-
-       /* for each expected completion, check it is there */
-       for (s = va_arg(ap, const char *);
-            s != EC_NODE_ENDLIST;
-            s = va_arg(ap, const char *)) {
-               struct ec_comp_iter *iter;
-               const struct ec_comp_item *item;
-
-               if (s == NULL) {
-                       ret = -1;
-                       goto out;
-               }
-
-               count++;
-
-               /* only check matching completions */
-               iter = ec_comp_iter(c, type);
-               while ((item = ec_comp_iter_next(iter)) != NULL) {
-                       const char *str = ec_comp_item_get_str(item);
-                       if (str != NULL && strcmp(str, s) == 0)
-                               break;
-               }
-
-               if (item == NULL) {
-                       EC_LOG(EC_LOG_ERR,
-                               "completion <%s> not in list\n", s);
-                       ret = -1;
-               }
-               ec_comp_iter_free(iter);
-       }
-
-       /* check if we have more completions (or less) than expected */
-       if (count != ec_comp_count(c, type)) {
-               EC_LOG(EC_LOG_ERR,
-                       "nb_completion (%d) does not match (%d)\n",
-                       count, ec_comp_count(c, type));
-               ec_comp_dump(stdout, c);
-               ret = -1;
-       }
-
-out:
-       ec_strvec_free(vec);
-       ec_comp_free(c);
-       va_end(ap);
-       return ret;
-}
-
-static int launch_test(const char *name)
-{
-       struct ec_test *test;
-       int ret = 0;
-       unsigned int count = 0;
-
-       TAILQ_FOREACH(test, &test_list, next) {
-               if (name != NULL && strcmp(name, test->name))
-                       continue;
-
-               EC_LOG(EC_LOG_INFO, "== starting test %-20s\n",
-                       test->name);
-
-               count++;
-               if (test->test() == 0) {
-                       EC_LOG(EC_LOG_INFO,
-                               "== test %-20s success\n",
-                               test->name);
-               } else {
-                       EC_LOG(EC_LOG_INFO,
-                               "== test %-20s failed\n",
-                               test->name);
-                       ret = -1;
-               }
-       }
-
-       if (name != NULL && count == 0) {
-               EC_LOG(EC_LOG_WARNING,
-                       "== test %s not found\n", name);
-               ret = -1;
-       }
-
-       return ret;
-}
-
-int ec_test_all(void)
-{
-       return launch_test(NULL);
-}
-
-int ec_test_one(const char *name)
-{
-       return launch_test(name);
-}
diff --git a/lib/ecoli_test.h b/lib/ecoli_test.h
deleted file mode 100644 (file)
index 94cd0b9..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_TEST_
-#define ECOLI_TEST_
-
-#include <sys/queue.h>
-
-#include <ecoli_log.h>
-
-struct ec_node;
-enum ec_comp_type;
-
-#define EC_TEST_REGISTER(t)                                            \
-       static void ec_test_init_##t(void);                             \
-       static void __attribute__((constructor, used))                  \
-       ec_test_init_##t(void)                                          \
-       {                                                               \
-               if (ec_test_register(&t) < 0)                           \
-                       fprintf(stderr, "cannot register test %s\n",    \
-                               t.name);                                \
-       }
-
-/**
- * Type of test function. Return 0 on success, -1 on error.
- */
-typedef int (ec_test_t)(void);
-
-TAILQ_HEAD(ec_test_list, ec_test);
-
-/**
- * A structure describing a test case.
- */
-struct ec_test {
-       TAILQ_ENTRY(ec_test) next;  /**< Next in list. */
-       const char *name;           /**< Test name. */
-       ec_test_t *test;            /**< Test function. */
-};
-
-/**
- * Register a test case.
- *
- * @param test
- *   A pointer to a ec_test structure describing the test
- *   to be registered.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_test_register(struct ec_test *test);
-
-int ec_test_all(void);
-int ec_test_one(const char *name);
-
-/* expected == -1 means no match */
-int ec_test_check_parse(struct ec_node *node, int expected, ...);
-
-#define EC_TEST_ERR(fmt, ...)                                          \
-       EC_LOG(EC_LOG_ERR, "%s:%d: error: " fmt "\n",                   \
-               __FILE__, __LINE__, ##__VA_ARGS__);                     \
-
-#define EC_TEST_CHECK(cond, fmt, ...) ({                               \
-       int ret_ = 0;                                                   \
-       if (!(cond)) {                                                  \
-               EC_TEST_ERR("(" #cond ") is wrong. " fmt                \
-                       ##__VA_ARGS__);                                 \
-               ret_ = -1;                                              \
-       }                                                               \
-       ret_;                                                           \
-})
-
-/* node, input, [expected1, expected2, ...] */
-#define EC_TEST_CHECK_PARSE(node, args...) ({                          \
-       int ret_ = ec_test_check_parse(node, args, EC_NODE_ENDLIST);    \
-       if (ret_)                                                       \
-               EC_TEST_ERR("parse test failed");                       \
-       ret_;                                                           \
-})
-
-int ec_test_check_complete(struct ec_node *node,
-                       enum ec_comp_type type, ...);
-
-#define EC_TEST_CHECK_COMPLETE(node, args...) ({                       \
-       int ret_ = ec_test_check_complete(node, EC_COMP_FULL, args);    \
-       if (ret_)                                                       \
-               EC_TEST_ERR("complete test failed");                    \
-       ret_;                                                           \
-})
-
-#define EC_TEST_CHECK_COMPLETE_PARTIAL(node, args...) ({               \
-       int ret_ = ec_test_check_complete(node, EC_COMP_PARTIAL, args); \
-       if (ret_)                                                       \
-               EC_TEST_ERR("complete test failed");                    \
-       ret_;                                                           \
-})
-
-#endif
diff --git a/lib/ecoli_vec.c b/lib/ecoli_vec.c
deleted file mode 100644 (file)
index fe8c572..0000000
+++ /dev/null
@@ -1,468 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/types.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <errno.h>
-#include <assert.h>
-
-#include <ecoli_assert.h>
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_vec.h>
-
-EC_LOG_TYPE_REGISTER(vec);
-
-struct ec_vec {
-       size_t len;
-       size_t size;
-       size_t elt_size;
-       ec_vec_elt_copy_t copy;
-       ec_vec_elt_free_t free;
-       void *vec;
-};
-
-static void *get_obj(const struct ec_vec *vec, size_t idx)
-{
-       assert(vec->elt_size != 0);
-       return (char *)vec->vec + (idx * vec->elt_size);
-}
-
-struct ec_vec *
-ec_vec(size_t elt_size, size_t size, ec_vec_elt_copy_t copy,
-       ec_vec_elt_free_t free)
-{
-       struct ec_vec *vec;
-
-       if (elt_size == 0) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       vec = ec_calloc(1, sizeof(*vec));
-       if (vec == NULL)
-               return NULL;
-
-       vec->elt_size = elt_size;
-       vec->copy = copy;
-       vec->free = free;
-
-       if (size == 0)
-               return vec;
-
-       vec->vec = ec_calloc(size, vec->elt_size);
-       if (vec->vec == NULL) {
-               ec_free(vec);
-               return NULL;
-       }
-
-       return vec;
-}
-
-int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr)
-{
-       void *new_vec;
-
-       if (vec->len + 1 > vec->size) {
-               new_vec = ec_realloc(vec->vec, vec->elt_size * (vec->len + 1));
-               if (new_vec == NULL)
-                       return -1;
-               vec->size = vec->len + 1;
-               vec->vec = new_vec;
-       }
-
-       memcpy(get_obj(vec, vec->len), ptr, vec->elt_size);
-       vec->len++;
-
-       return 0;
-}
-
-int ec_vec_add_ptr(struct ec_vec *vec, void *elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-struct ec_vec *ec_vec_ndup(const struct ec_vec *vec, size_t off,
-       size_t len)
-{
-       struct ec_vec *copy = NULL;
-       size_t i, veclen;
-
-       veclen = ec_vec_len(vec);
-       if (off + len > veclen)
-               return NULL;
-
-       copy = ec_vec(vec->elt_size, len, vec->copy, vec->free);
-       if (copy == NULL)
-               goto fail;
-
-       if (len == 0)
-               return copy;
-
-       for (i = 0; i < len; i++) {
-               if (vec->copy)
-                       vec->copy(get_obj(copy, i), get_obj(vec, i + off));
-               else
-                       memcpy(get_obj(copy, i), get_obj(vec, i + off),
-                               vec->elt_size);
-       }
-       copy->len = len;
-
-       return copy;
-
-fail:
-       ec_vec_free(copy);
-       return NULL;
-}
-
-size_t ec_vec_len(const struct ec_vec *vec)
-{
-       if (vec == NULL)
-               return 0;
-
-       return vec->len;
-}
-
-struct ec_vec *ec_vec_dup(const struct ec_vec *vec)
-{
-       return ec_vec_ndup(vec, 0, ec_vec_len(vec));
-}
-
-void ec_vec_free(struct ec_vec *vec)
-{
-       size_t i;
-
-       if (vec == NULL)
-               return;
-
-       for (i = 0; i < ec_vec_len(vec); i++) {
-               if (vec->free)
-                       vec->free(get_obj(vec, i));
-       }
-
-       ec_free(vec->vec);
-       ec_free(vec);
-}
-
-int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx)
-{
-       if (vec == NULL || idx >= vec->len) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       memcpy(ptr, get_obj(vec, idx), vec->elt_size);
-
-       return 0;
-}
-
-static void str_free(void *elt)
-{
-       char **s = elt;
-
-       ec_free(*s);
-}
-
-#define GOTO_FAIL do {                                      \
-               EC_LOG(EC_LOG_ERR, "%s:%d: test failed\n",   \
-                       __FILE__, __LINE__);                 \
-               goto fail;                                   \
-       } while(0)
-
-/* LCOV_EXCL_START */
-static int ec_vec_testcase(void)
-{
-       struct ec_vec *vec = NULL;
-       struct ec_vec *vec2 = NULL;
-       uint8_t val8;
-       uint16_t val16;
-       uint32_t val32;
-       uint64_t val64;
-       void *valp;
-       char *vals;
-
-       /* uint8_t vector */
-       vec = ec_vec(sizeof(val8), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_u8(vec, 0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u8(vec, 1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u8(vec, 2) < 0)
-               GOTO_FAIL;
-       /* should fail */
-       if (ec_vec_add_u16(vec, 3) == 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u32(vec, 3) == 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u64(vec, 3) == 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, (void *)3) == 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&val8, vec, 0) < 0)
-               GOTO_FAIL;
-       if (val8 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec, 1) < 0)
-               GOTO_FAIL;
-       if (val8 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec, 2) < 0)
-               GOTO_FAIL;
-       if (val8 != 2)
-               GOTO_FAIL;
-
-       /* duplicate the vector */
-       vec2 = ec_vec_dup(vec);
-       if (vec2 == NULL)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 0) < 0)
-               GOTO_FAIL;
-       if (val8 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 1) < 0)
-               GOTO_FAIL;
-       if (val8 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 2) < 0)
-               GOTO_FAIL;
-       if (val8 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec2);
-       vec2 = NULL;
-
-       /* dup at offset 1 */
-       vec2 = ec_vec_ndup(vec, 1, 2);
-       if (vec2 == NULL)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 0) < 0)
-               GOTO_FAIL;
-       if (val8 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 1) < 0)
-               GOTO_FAIL;
-       if (val8 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec2);
-       vec2 = NULL;
-
-       /* len = 0, duplicate is empty */
-       vec2 = ec_vec_ndup(vec, 2, 0);
-       if (vec2 == NULL)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 0) == 0)
-               GOTO_FAIL;
-
-       ec_vec_free(vec2);
-       vec2 = NULL;
-
-       /* bad dup args */
-       vec2 = ec_vec_ndup(vec, 10, 1);
-       if (vec2 != NULL)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* uint16_t vector */
-       vec = ec_vec(sizeof(val16), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_u16(vec, 0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u16(vec, 1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u16(vec, 2) < 0)
-               GOTO_FAIL;
-       /* should fail */
-       if (ec_vec_add_u8(vec, 3) == 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&val16, vec, 0) < 0)
-               GOTO_FAIL;
-       if (val16 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val16, vec, 1) < 0)
-               GOTO_FAIL;
-       if (val16 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val16, vec, 2) < 0)
-               GOTO_FAIL;
-       if (val16 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* uint32_t vector */
-       vec = ec_vec(sizeof(val32), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_u32(vec, 0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u32(vec, 1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u32(vec, 2) < 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&val32, vec, 0) < 0)
-               GOTO_FAIL;
-       if (val32 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val32, vec, 1) < 0)
-               GOTO_FAIL;
-       if (val32 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val32, vec, 2) < 0)
-               GOTO_FAIL;
-       if (val32 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* uint64_t vector */
-       vec = ec_vec(sizeof(val64), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_u64(vec, 0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u64(vec, 1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u64(vec, 2) < 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&val64, vec, 0) < 0)
-               GOTO_FAIL;
-       if (val64 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val64, vec, 1) < 0)
-               GOTO_FAIL;
-       if (val64 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val64, vec, 2) < 0)
-               GOTO_FAIL;
-       if (val64 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* ptr vector */
-       vec = ec_vec(sizeof(valp), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_ptr(vec, (void *)0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, (void *)1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, (void *)2) < 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&valp, vec, 0) < 0)
-               GOTO_FAIL;
-       if (valp != (void *)0)
-               GOTO_FAIL;
-       if (ec_vec_get(&valp, vec, 1) < 0)
-               GOTO_FAIL;
-       if (valp != (void *)1)
-               GOTO_FAIL;
-       if (ec_vec_get(&valp, vec, 2) < 0)
-               GOTO_FAIL;
-       if (valp != (void *)2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* string vector */
-       vec = ec_vec(sizeof(valp), 0, NULL, str_free);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_ptr(vec, ec_strdup("0")) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, ec_strdup("1")) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, ec_strdup("2")) < 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&vals, vec, 0) < 0)
-               GOTO_FAIL;
-       if (vals == NULL || strcmp(vals, "0"))
-               GOTO_FAIL;
-       if (ec_vec_get(&vals, vec, 1) < 0)
-               GOTO_FAIL;
-       if (vals == NULL || strcmp(vals, "1"))
-               GOTO_FAIL;
-       if (ec_vec_get(&vals, vec, 2) < 0)
-               GOTO_FAIL;
-       if (vals == NULL || strcmp(vals, "2"))
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* invalid args */
-       vec = ec_vec(0, 0, NULL, NULL);
-       if (vec != NULL)
-               GOTO_FAIL;
-
-       return 0;
-
-fail:
-       ec_vec_free(vec);
-       ec_vec_free(vec2);
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_vec_test = {
-       .name = "vec",
-       .test = ec_vec_testcase,
-};
-
-EC_TEST_REGISTER(ec_vec_test);
diff --git a/lib/ecoli_vec.h b/lib/ecoli_vec.h
deleted file mode 100644 (file)
index 5fdaa99..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Vectors of objects.
- *
- * The ec_vec API provide helpers to manipulate vectors of objects
- * of any kind.
- */
-
-#ifndef ECOLI_VEC_
-#define ECOLI_VEC_
-
-#include <sys/types.h>
-#include <stdint.h>
-#include <stdio.h>
-
-/* if NULL, default does nothing */
-typedef void (*ec_vec_elt_free_t)(void *ptr);
-
-/* if NULL, default is:
- * memcpy(dst, src, vec->elt_size)
- */
-typedef void (*ec_vec_elt_copy_t)(void *dst, void *src);
-
-struct ec_vec *ec_vec(size_t elt_size, size_t size,
-               ec_vec_elt_copy_t copy, ec_vec_elt_free_t free);
-int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr);
-
-int ec_vec_add_ptr(struct ec_vec *vec, void *elt);
-int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt);
-int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt);
-int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt);
-int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt);
-
-int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx);
-
-struct ec_vec *ec_vec_dup(const struct ec_vec *vec);
-struct ec_vec *ec_vec_ndup(const struct ec_vec *vec,
-       size_t off, size_t len);
-void ec_vec_free(struct ec_vec *vec);
-
-__attribute__((pure))
-size_t ec_vec_len(const struct ec_vec *vec);
-
-#endif
diff --git a/lib/main-readline.c b/lib/main-readline.c
deleted file mode 100644 (file)
index 2c81c53..0000000
+++ /dev/null
@@ -1,361 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#define _GNU_SOURCE /* for asprintf */
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <assert.h>
-
-#include <readline/readline.h>
-#include <readline/history.h>
-
-#include <ecoli_init.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_keyval.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_space.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_sh_lex.h>
-#include <ecoli_node_int.h>
-#include <ecoli_node_option.h>
-#include <ecoli_node_cmd.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_once.h>
-#include <ecoli_node_file.h>
-
-static struct ec_node *commands;
-
-static char *my_completion_entry(const char *s, int state)
-{
-       static struct ec_comp *c;
-       static struct ec_comp_iter *iter;
-       const struct ec_comp_item *item;
-       enum ec_comp_type item_type;
-       const char *item_str, *item_display;
-
-       (void)s;
-
-       /* Don't append a quote. Note: there are still some bugs when
-        * completing a quoted token. */
-       rl_completion_suppress_quote = 1;
-       rl_completer_quote_characters = "\"'";
-
-       if (state == 0) {
-               char *line;
-
-               ec_comp_free(c);
-               line = strdup(rl_line_buffer);
-               if (line == NULL)
-                       return NULL;
-               line[rl_point] = '\0';
-
-               c = ec_node_complete(commands, line);
-               free(line);
-               if (c == NULL)
-                       return NULL;
-
-               ec_comp_iter_free(iter);
-               iter = ec_comp_iter(c, EC_COMP_FULL | EC_COMP_PARTIAL);
-               if (iter == NULL)
-                       return NULL;
-       }
-
-       item = ec_comp_iter_next(iter);
-       if (item == NULL)
-               return NULL;
-
-       item_str = ec_comp_item_get_str(item);
-       if (c->count_full == 1) {
-
-               /* don't add the trailing space for partial completions */
-               if (state == 0) {
-                       item_type = ec_comp_item_get_type(item);
-                       if (item_type == EC_COMP_FULL)
-                               rl_completion_suppress_append = 0;
-                       else
-                               rl_completion_suppress_append = 1;
-               }
-
-               return strdup(item_str);
-       } else if (rl_completion_type == '?') {
-               /* on second try only show the display part */
-               item_display = ec_comp_item_get_display(item);
-               return strdup(item_display);
-       }
-
-       return strdup(item_str);
-}
-
-static char **my_attempted_completion(const char *text, int start, int end)
-{
-       (void)start;
-       (void)end;
-
-       /* remove default file completion */
-       rl_attempted_completion_over = 1;
-
-       return rl_completion_matches(text, my_completion_entry);
-}
-
-/* this function builds the help string */
-static char *get_node_help(const struct ec_comp_item *item)
-{
-       const struct ec_comp_group *grp;
-       const struct ec_parse *state;
-       const struct ec_node *node;
-       char *help = NULL;
-       const char *node_help = NULL;
-       const char *node_desc = NULL;
-
-       grp = ec_comp_item_get_grp(item);
-       state = grp->state;
-       for (state = grp->state; state != NULL;
-            state = ec_parse_get_parent(state)) {
-               node = ec_parse_get_node(state);
-               if (node_help == NULL)
-                       node_help = ec_keyval_get(ec_node_attrs(node), "help");
-               if (node_desc == NULL)
-                       node_desc = ec_node_desc(node);
-       }
-
-       if (node_help == NULL)
-               node_help = "-";
-       if (node_desc == NULL)
-               return NULL;
-
-       if (asprintf(&help, "%-20s %s", node_desc, node_help) < 0)
-               return NULL;
-
-       return help;
-}
-
-static int show_help(int ignore, int invoking_key)
-{
-       struct ec_comp_iter *iter = NULL;
-       const struct ec_comp_group *grp, *prev_grp = NULL;
-       const struct ec_comp_item *item;
-       struct ec_comp *c = NULL;
-       struct ec_parse *p = NULL;
-       char *line = NULL;
-       unsigned int count;
-       char **helps = NULL;
-       int match = 0;
-       int cols;
-
-       (void)ignore;
-       (void)invoking_key;
-
-       line = strdup(rl_line_buffer);
-       if (line == NULL)
-               goto fail;
-
-       /* check if the current line matches */
-       p = ec_node_parse(commands, line);
-       if (ec_parse_matches(p))
-               match = 1;
-       ec_parse_free(p);
-       p = NULL;
-
-       /* complete at current cursor position */
-       line[rl_point] = '\0';
-       c = ec_node_complete(commands, line);
-       free(line);
-       line = NULL;
-       if (c == NULL)
-               goto fail;
-
-       /* let's display one contextual help per node */
-       count = 0;
-       iter = ec_comp_iter(c,
-               EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL);
-       if (iter == NULL)
-               goto fail;
-
-       /* strangely, rl_display_match_list() expects first index at 1 */
-       helps = calloc(match + 1, sizeof(char *));
-       if (helps == NULL)
-               goto fail;
-       if (match)
-               helps[1] = "<return>";
-
-       while ((item = ec_comp_iter_next(iter)) != NULL) {
-               char **tmp;
-
-               /* keep one help per group, skip other items  */
-               grp = ec_comp_item_get_grp(item);
-               if (grp == prev_grp)
-                       continue;
-
-               prev_grp = grp;
-
-               tmp = realloc(helps, (count + match + 2) * sizeof(char *));
-               if (tmp == NULL)
-                       goto fail;
-               helps = tmp;
-               helps[count + match + 1] = get_node_help(item);
-               count++;
-       }
-
-       ec_comp_iter_free(iter);
-       ec_comp_free(c);
-       /* ensure not more than 1 entry per line */
-       rl_get_screen_size(NULL, &cols);
-       rl_display_match_list(helps, count + match, cols);
-       rl_forced_update_display();
-
-       return 0;
-
-fail:
-       ec_comp_iter_free(iter);
-       ec_parse_free(p);
-       free(line);
-       ec_comp_free(c);
-       if (helps != NULL) {
-               while (count--)
-                       free(helps[count + match + 1]);
-       }
-       free(helps);
-
-       return 1;
-}
-
-static int create_commands(void)
-{
-       struct ec_node *cmdlist = NULL, *cmd = NULL;
-
-       cmdlist = ec_node("or", EC_NO_ID);
-       if (cmdlist == NULL)
-               goto fail;
-
-
-       cmd = EC_NODE_SEQ(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "hello"),
-               EC_NODE_OR("name",
-                       ec_node_str("john", "john"),
-                       ec_node_str(EC_NO_ID, "johnny"),
-                       ec_node_str(EC_NO_ID, "mike")
-               ),
-               ec_node_option(EC_NO_ID, ec_node_int("int", 0, 10, 10))
-       );
-       if (cmd == NULL)
-               goto fail;
-       ec_keyval_set(ec_node_attrs(cmd), "help",
-               "say hello to someone several times", NULL);
-       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "john")),
-               "help", "specific help for john", NULL);
-       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")),
-               "help", "the name of the person", NULL);
-       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "int")),
-               "help", "an integer (0-10)", NULL);
-       if (ec_node_or_add(cmdlist, cmd) < 0)
-               goto fail;
-
-
-       cmd = EC_NODE_CMD(EC_NO_ID, "good morning name [count]",
-                       EC_NODE_CMD("name", "bob|bobby|michael"),
-                       ec_node_int("count", 0, 10, 10));
-       if (cmd == NULL)
-               goto fail;
-       ec_keyval_set(ec_node_attrs(cmd), "help",
-               "say good morning to someone several times", NULL);
-       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), "help",
-               "the person to greet", NULL);
-       ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "count")), "help",
-               "how many times to greet (0-10)", NULL);
-       if (ec_node_or_add(cmdlist, cmd) < 0)
-               goto fail;
-
-
-       cmd = EC_NODE_CMD(EC_NO_ID,
-                       "buy potatoes,carrots,pumpkins");
-       if (cmd == NULL)
-               goto fail;
-       ec_keyval_set(ec_node_attrs(cmd), "help",
-               "buy some vegetables", NULL);
-       if (ec_node_or_add(cmdlist, cmd) < 0)
-               goto fail;
-
-
-       cmd = EC_NODE_CMD(EC_NO_ID, "eat vegetables",
-                       ec_node_many("vegetables",
-                               EC_NODE_OR(EC_NO_ID,
-                                       ec_node_str(EC_NO_ID, "potatoes"),
-                                       ec_node_once(EC_NO_ID,
-                                               ec_node_str(EC_NO_ID, "carrots")),
-                                       ec_node_once(EC_NO_ID,
-                                               ec_node_str(EC_NO_ID, "pumpkins"))),
-                       1, 0));
-       if (cmd == NULL)
-               goto fail;
-       ec_keyval_set(ec_node_attrs(cmd), "help",
-               "eat vegetables (take some more potatoes)", NULL);
-       if (ec_node_or_add(cmdlist, cmd) < 0)
-               goto fail;
-
-
-       cmd = EC_NODE_SEQ(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "bye")
-       );
-       ec_keyval_set(ec_node_attrs(cmd), "help", "say bye", NULL);
-       if (ec_node_or_add(cmdlist, cmd) < 0)
-               goto fail;
-
-
-       cmd = EC_NODE_SEQ(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "load"),
-               ec_node("file", EC_NO_ID)
-       );
-       ec_keyval_set(ec_node_attrs(cmd), "help", "load a file", NULL);
-       if (ec_node_or_add(cmdlist, cmd) < 0)
-               goto fail;
-
-
-       commands = ec_node_sh_lex(EC_NO_ID, cmdlist);
-       if (commands == NULL)
-               goto fail;
-
-       return 0;
-
- fail:
-       fprintf(stderr, "cannot initialize nodes\n");
-       ec_node_free(cmdlist);
-       return -1;
-}
-
-int main(void)
-{
-       struct ec_parse *p;
-       char *line;
-
-       if (ec_init() < 0) {
-               fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
-               return 1;
-       }
-
-       if (create_commands() < 0)
-               return 1;
-
-       rl_bind_key('?', show_help);
-       rl_attempted_completion_function = my_attempted_completion;
-
-       while (1) {
-               line = readline("> ");
-               if (line == NULL)
-                       break;
-
-               p = ec_node_parse(commands, line);
-               ec_parse_dump(stdout, p);
-               add_history(line);
-               ec_parse_free(p);
-       }
-
-
-       ec_node_free(commands);
-       return 0;
-
-}
diff --git a/lib/main.c b/lib/main.c
deleted file mode 100644 (file)
index 1b2b7ef..0000000
+++ /dev/null
@@ -1,407 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <assert.h>
-#include <getopt.h>
-#include <limits.h>
-#include <execinfo.h>
-#include <errno.h>
-
-#include <ecoli_init.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_malloc.h>
-
-/* LCOV_EXCL_START */
-EC_LOG_TYPE_REGISTER(main);
-
-#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 int seed = 0;
-static size_t alloc_success = 0;
-
-static const char ec_short_options[] =
-       "h"  /* help */
-       "l:" /* log-level */
-       "r:" /* random-alloc-fail */
-       "s:" /* seed */
-       ;
-
-#define EC_OPT_HELP "help"
-#define EC_OPT_LOG_LEVEL "log-level"
-#define EC_OPT_RANDOM_ALLOC_FAIL "random-alloc-fail"
-#define EC_OPT_SEED "seed"
-
-static const struct option ec_long_options[] = {
-       {EC_OPT_HELP, 1, NULL, 'h'},
-       {EC_OPT_LOG_LEVEL, 1, NULL, 'l'},
-       {EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'},
-       {EC_OPT_SEED, 1, NULL, 's'},
-       {NULL, 0, NULL, 0}
-};
-
-static void usage(const char *prgname)
-{
-       printf("%s [options] [test1 test2 test3...]\n"
-               "  -h\n"
-               "  --"EC_OPT_HELP"\n"
-               "      Show this help.\n"
-               "  -l <level>\n"
-               "  --"EC_OPT_LOG_LEVEL"=<level>\n"
-               "      Set log level (0 = no log, 7 = verbose).\n"
-               "  -r <probability>\n"
-               "  --"EC_OPT_RANDOM_ALLOC_FAIL"=<probability>\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"
-               "  -s <seed>\n"
-               "  --seed=<seed>\n"
-               "      Seeds the random number generator. Default is 0.\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 int parse_args(int argc, char **argv)
-{
-       int ret, opt;
-
-       while ((opt = getopt_long(argc, argv, ec_short_options,
-                               ec_long_options, NULL)) != EOF) {
-
-               switch (opt) {
-               case 'h': /* help */
-                       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;
-
-               case 's': /* seed */
-                       if (parse_int(optarg, 0, INT_MAX, &seed, 10) < 0) {
-                               printf("Invalid seed value\n");
-                               usage(argv[0]);
-                               exit(1);
-                       }
-                       break;
-
-               default:
-                       usage(argv[0]);
-                       return -1;
-               }
-       }
-
-       ret = optind - 1;
-       optind = 1;
-
-       return ret;
-}
-
-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 seq;
-       unsigned int line;
-       size_t size;
-       void *stack[STACK_SZ];
-       int stacklen;
-       unsigned int cookie;
-};
-
-struct debug_alloc_ftr {
-       unsigned int cookie;
-} __attribute__((packed));
-
-static int malloc_seq;
-
-static void *debug_malloc(size_t size, const char *file, unsigned int line)
-{
-       struct debug_alloc_hdr *hdr;
-       struct debug_alloc_ftr *ftr;
-       size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
-       void *ret;
-       int r = random();
-
-       if (alloc_fail_proba != 0 && (r % 100) < alloc_fail_proba)
-               hdr = NULL;
-       else
-               hdr = malloc(new_size);
-
-       if (hdr == NULL) {
-               ret = NULL;
-       } else {
-               hdr->seq = malloc_seq;
-               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 = 0x87654321;
-       }
-
-       EC_LOG(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p seq=%d\n",
-               file, line, size, ret, malloc_seq++);
-
-       if (ret)
-               alloc_success++;
-       return ret;
-}
-
-static void debug_free(void *ptr, const char *file, unsigned int line)
-{
-       struct debug_alloc_hdr *hdr, *h;
-       struct debug_alloc_ftr *ftr;
-
-       (void)file;
-       (void)line;
-
-       EC_LOG(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
-
-       if (ptr == NULL)
-               return;
-
-       hdr = (ptr - sizeof(*hdr));
-       if (hdr->cookie != 0x12345678) {
-               EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
-                       file, line, ptr);
-               abort();
-       }
-
-       ftr = (ptr + hdr->size);
-       if (ftr->cookie != 0x87654321) {
-               EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
-                       file, line, ptr);
-               abort();
-       }
-
-       TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
-               if (h == hdr)
-                       break;
-       }
-
-       if (h == NULL) {
-               EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
-                       file, line, ptr);
-               abort();
-       }
-
-       TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
-       free(hdr);
-}
-
-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;
-       size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
-       void *ret;
-
-       if (ptr != NULL) {
-               hdr =  (ptr - sizeof(*hdr));
-               if (hdr->cookie != 0x12345678) {
-                       EC_LOG(EC_LOG_ERR,
-                               "%s:%d: error: realloc(%p): bad start cookie\n",
-                               file, line, ptr);
-                       abort();
-               }
-
-               ftr = (ptr + hdr->size);
-               if (ftr->cookie != 0x87654321) {
-                       EC_LOG(EC_LOG_ERR,
-                               "%s:%d: error: realloc(%p): bad end cookie\n",
-                               file, line, ptr);
-                       abort();
-               }
-
-               TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
-                       if (h == hdr)
-                               break;
-               }
-
-               if (h == NULL) {
-                       EC_LOG(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
-                               file, line, ptr);
-                       abort();
-               }
-
-               TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
-               hdr = realloc(hdr, new_size);
-               if (hdr == NULL) {
-                       TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
-                       ret = NULL;
-               } else {
-                       ret = hdr + 1;
-               }
-       } else {
-               hdr = realloc(NULL, new_size);
-               if (hdr == NULL)
-                       ret = NULL;
-               else
-                       ret = hdr + 1;
-       }
-
-       if (hdr != NULL) {
-               hdr->seq = malloc_seq;
-               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 = 0x87654321;
-       }
-
-       EC_LOG(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p seq=%d\n",
-               file, line, ptr, size, ret, malloc_seq++);
-
-       if (ret)
-               alloc_success++;
-       return ret;
-}
-
-static int debug_alloc_dump_leaks(void)
-{
-       struct debug_alloc_hdr *hdr;
-       int i;
-       char **buffer;
-
-       EC_LOG(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
-
-       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 seq=%u size=%zd ptr=%p\n",
-                       hdr->file, hdr->line, hdr->seq, 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 <prog> <addr>\n");
-
-       return -1;
-}
-
-static int debug_log(int type, unsigned int level, void *opaque,
-               const char *str)
-{
-       (void)type;
-       (void)opaque;
-
-       if (level > (unsigned int)log_level)
-               return 0;
-
-       if (printf("%s", str) < 0)
-               return -1;
-
-       return 0;
-}
-
-int main(int argc, char **argv)
-{
-       int i, ret = 0, leaks;
-
-       ret = parse_args(argc, argv);
-       if (ret < 0)
-               return 1;
-
-       argc -= ret;
-       argv += ret;
-
-       srandom(seed);
-
-       /* 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;
-       }
-
-       if (ec_init() < 0) {
-               fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
-               return 1;
-       }
-       ec_log_fct_register(debug_log, NULL);
-
-       ret = 0;
-       if (argc <= 1) {
-               ret = ec_test_all();
-       } else {
-               for (i = 1; i < argc; i++)
-                       ret |= ec_test_one(argv[i]);
-       }
-
-       leaks = debug_alloc_dump_leaks();
-
-       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;
-}
-/* LCOV_EXCL_STOP */
diff --git a/lib/parse-yaml.c b/lib/parse-yaml.c
deleted file mode 100644 (file)
index 48bcdc7..0000000
+++ /dev/null
@@ -1,552 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-#include <errno.h>
-#include <limits.h>
-#include <assert.h>
-
-#include <yaml.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-
-/* associate a yaml node to a ecoli node */
-struct pair {
-       const yaml_node_t *ynode;
-       struct ec_node *enode;
-};
-
-/* store the ecoli node tree and the associations yaml_node <-> ec_node */
-struct enode_tree {
-       struct ec_node *root;
-       struct pair *table;
-       size_t table_len;
-};
-
-static struct ec_node *
-parse_ec_node(struct enode_tree *tree,
-       const yaml_document_t *document, const yaml_node_t *ynode);
-
-static struct ec_config *
-parse_ec_config_list(struct enode_tree *tree,
-               const struct ec_config_schema *schema,
-               const yaml_document_t *document, const yaml_node_t *ynode);
-
-static struct ec_config *
-parse_ec_config_dict(struct enode_tree *tree,
-               const struct ec_config_schema *schema,
-               const yaml_document_t *document, const yaml_node_t *ynode);
-
-/* XXX to utils.c ? */
-static int
-parse_llint(const char *str, int64_t *val)
-{
-       char *endptr;
-       int save_errno = errno;
-
-       errno = 0;
-       *val = strtoll(str, &endptr, 0);
-
-       if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
-                       (errno != 0 && *val == 0))
-               return -1;
-
-       if (*endptr != 0) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       errno = save_errno;
-       return 0;
-}
-
-static int
-parse_ullint(const char *str, uint64_t *val)
-{
-       char *endptr;
-       int save_errno = errno;
-
-       /* since a negative input is silently converted to a positive
-        * one by strtoull(), first check that it is positive */
-       if (strchr(str, '-'))
-               return -1;
-
-       errno = 0;
-       *val = strtoull(str, &endptr, 0);
-
-       if ((errno == ERANGE && *val == ULLONG_MAX) ||
-                       (errno != 0 && *val == 0))
-               return -1;
-
-       if (*endptr != 0)
-               return -1;
-
-       errno = save_errno;
-       return 0;
-}
-
-static int
-parse_bool(const char *str, bool *val)
-{
-       if (!strcasecmp(str, "true")) {
-               *val = true;
-               return 0;
-       } else if (!strcasecmp(str, "false")) {
-               *val = false;
-               return 0;
-       }
-       errno = EINVAL;
-       return -1;
-}
-
-static int
-add_in_table(struct enode_tree *tree, const yaml_node_t *ynode,
-       struct ec_node *enode)
-{
-       struct pair *table = NULL;
-
-       table = realloc(tree->table, (tree->table_len + 1) * sizeof(*table));
-       if (table == NULL)
-               return -1;
-
-       ec_node_clone(enode);
-       table[tree->table_len].ynode = ynode;
-       table[tree->table_len].enode = enode;
-       tree->table = table;
-       tree->table_len++;
-
-       return 0;
-}
-
-static void
-free_tree(struct enode_tree *tree)
-{
-       size_t i;
-
-       if (tree->root != NULL)
-               ec_node_free(tree->root);
-       for (i = 0; i < tree->table_len; i++)
-               ec_node_free(tree->table[i].enode);
-       free(tree->table);
-}
-
-static struct ec_config *
-parse_ec_config(struct enode_tree *tree,
-               const struct ec_config_schema *schema_elt,
-               const yaml_document_t *document, const yaml_node_t *ynode)
-{
-       const struct ec_config_schema *subschema;
-       struct ec_config *config = NULL;
-       struct ec_node *enode = NULL;
-       enum ec_config_type type;
-       const char *value_str;
-       uint64_t u64;
-       int64_t i64;
-       bool boolean;
-
-       type = ec_config_schema_type(schema_elt);
-
-       switch (type) {
-       case EC_CONFIG_TYPE_BOOL:
-               if (ynode->type != YAML_SCALAR_NODE) {
-                       fprintf(stderr, "Boolean should be scalar\n");
-                       goto fail;
-               }
-               value_str = (const char *)ynode->data.scalar.value;
-               if (parse_bool(value_str, &boolean)  < 0) {
-                       fprintf(stderr, "Failed to parse boolean\n");
-                       goto fail;
-               }
-               config = ec_config_bool(boolean);
-               if (config == NULL) {
-                       fprintf(stderr, "Failed to create config\n");
-                       goto fail;
-               }
-               break;
-       case EC_CONFIG_TYPE_INT64:
-               if (ynode->type != YAML_SCALAR_NODE) {
-                       fprintf(stderr, "Int64 should be scalar\n");
-                       goto fail;
-               }
-               value_str = (const char *)ynode->data.scalar.value;
-               if (parse_llint(value_str, &i64)  < 0) {
-                       fprintf(stderr, "Failed to parse i64\n");
-                       goto fail;
-               }
-               config = ec_config_i64(i64);
-               if (config == NULL) {
-                       fprintf(stderr, "Failed to create config\n");
-                       goto fail;
-               }
-               break;
-       case EC_CONFIG_TYPE_UINT64:
-               if (ynode->type != YAML_SCALAR_NODE) {
-                       fprintf(stderr, "Uint64 should be scalar\n");
-                       goto fail;
-               }
-               value_str = (const char *)ynode->data.scalar.value;
-               if (parse_ullint(value_str, &u64)  < 0) {
-                       fprintf(stderr, "Failed to parse u64\n");
-                       goto fail;
-               }
-               config = ec_config_u64(u64);
-               if (config == NULL) {
-                       fprintf(stderr, "Failed to create config\n");
-                       goto fail;
-               }
-               break;
-       case EC_CONFIG_TYPE_STRING:
-               if (ynode->type != YAML_SCALAR_NODE) {
-                       fprintf(stderr, "String should be scalar\n");
-                       goto fail;
-               }
-               value_str = (const char *)ynode->data.scalar.value;
-               config = ec_config_string(value_str);
-               if (config == NULL) {
-                       fprintf(stderr, "Failed to create config\n");
-                       goto fail;
-               }
-               break;
-       case EC_CONFIG_TYPE_NODE:
-               enode = parse_ec_node(tree, document, ynode);
-               if (enode == NULL)
-                       goto fail;
-               config = ec_config_node(enode);
-               if (config == NULL) {
-                       fprintf(stderr, "Failed to create config\n");
-                       goto fail;
-               }
-               break;
-       case EC_CONFIG_TYPE_LIST:
-               subschema = ec_config_schema_sub(schema_elt);
-               if (subschema == NULL) {
-                       fprintf(stderr, "List has no subschema\n");
-                       goto fail;
-               }
-               config = parse_ec_config_list(tree, subschema, document, ynode);
-               if (config == NULL)
-                       goto fail;
-               break;
-       case EC_CONFIG_TYPE_DICT:
-               subschema = ec_config_schema_sub(schema_elt);
-               if (subschema == NULL) {
-                       fprintf(stderr, "Dict has no subschema\n");
-                       goto fail;
-               }
-               config = parse_ec_config_dict(tree, subschema, document, ynode);
-               if (config == NULL)
-                       goto fail;
-               break;
-       default:
-               fprintf(stderr, "Invalid config type %d\n", type);
-               goto fail;
-       }
-
-       return config;
-
-fail:
-       ec_node_free(enode);
-       ec_config_free(config);
-       return NULL;
-}
-
-static struct ec_config *
-parse_ec_config_list(struct enode_tree *tree,
-               const struct ec_config_schema *schema,
-               const yaml_document_t *document, const yaml_node_t *ynode)
-{
-       struct ec_config *config = NULL, *subconfig = NULL;
-       const yaml_node_item_t *item;
-       const yaml_node_t *value;
-
-       (void)tree;
-       (void)schema;
-       (void)document;
-
-       if (ynode->type != YAML_SEQUENCE_NODE) {
-               fprintf(stderr, "Ecoli list config should be a yaml sequence\n");
-               goto fail;
-       }
-
-       config = ec_config_list();
-       if (config == NULL) {
-               fprintf(stderr, "Failed to allocate config\n");
-               goto fail;
-       }
-
-       for (item = ynode->data.sequence.items.start;
-            item < ynode->data.sequence.items.top; item++) {
-               value = document->nodes.start + (*item) - 1; // XXX -1 ?
-               subconfig = parse_ec_config(tree, schema, document, value);
-               if (subconfig == NULL)
-                       goto fail;
-               if (ec_config_list_add(config, subconfig) < 0) {
-                       fprintf(stderr, "Failed to add list entry\n");
-                       goto fail;
-               }
-       }
-
-       return config;
-
-fail:
-       ec_config_free(config);
-       return NULL;
-}
-
-static struct ec_config *
-parse_ec_config_dict(struct enode_tree *tree,
-               const struct ec_config_schema *schema,
-               const yaml_document_t *document, const yaml_node_t *ynode)
-{
-       const struct ec_config_schema *schema_elt;
-       struct ec_config *config = NULL, *subconfig = NULL;
-       const yaml_node_t *key, *value;
-       const yaml_node_pair_t *pair;
-       const char *key_str;
-
-       if (ynode->type != YAML_MAPPING_NODE) {
-               fprintf(stderr, "Ecoli config should be a yaml mapping node\n");
-               goto fail;
-       }
-
-       config = ec_config_dict();
-       if (config == NULL) {
-               fprintf(stderr, "Failed to allocate config\n");
-               goto fail;
-       }
-
-       for (pair = ynode->data.mapping.pairs.start;
-            pair < ynode->data.mapping.pairs.top; pair++) {
-               key = document->nodes.start + pair->key - 1; // XXX -1 ?
-               value = document->nodes.start + pair->value - 1;
-               key_str = (const char *)key->data.scalar.value;
-
-               if (ec_config_key_is_reserved(key_str))
-                       continue;
-               schema_elt = ec_config_schema_lookup(schema, key_str);
-               if (schema_elt == NULL) {
-                       fprintf(stderr, "No such config %s\n", key_str);
-                       goto fail;
-               }
-               subconfig = parse_ec_config(tree, schema_elt, document, value);
-               if (subconfig == NULL)
-                       goto fail;
-               if (ec_config_dict_set(config, key_str, subconfig) < 0) {
-                       fprintf(stderr, "Failed to set dict entry\n");
-                       goto fail;
-               }
-       }
-
-       return config;
-
-fail:
-       ec_config_free(config);
-       return NULL;
-}
-
-static struct ec_node *
-parse_ec_node(struct enode_tree *tree,
-       const yaml_document_t *document, const yaml_node_t *ynode)
-{
-       const struct ec_config_schema *schema;
-       const struct ec_node_type *type = NULL;
-       const char *id = NULL, *help = NULL;
-       struct ec_config *config = NULL;
-       const yaml_node_t *attrs = NULL;
-       const yaml_node_t *key, *value;
-       const yaml_node_pair_t *pair;
-       const char *key_str, *value_str;
-       struct ec_node *enode = NULL;
-
-       if (ynode->type != YAML_MAPPING_NODE) {
-               fprintf(stderr, "Ecoli node should be a yaml mapping node\n");
-               goto fail;
-       }
-
-       for (pair = ynode->data.mapping.pairs.start;
-            pair < ynode->data.mapping.pairs.top; pair++) {
-               key = document->nodes.start + pair->key - 1; // XXX -1 ?
-               value = document->nodes.start + pair->value - 1;
-               key_str = (const char *)key->data.scalar.value;
-               value_str = (const char *)value->data.scalar.value;
-
-               if (!strcmp(key_str, "type")) {
-                       if (type != NULL) {
-                               fprintf(stderr, "Duplicate type\n");
-                               goto fail;
-                       }
-                       if (value->type != YAML_SCALAR_NODE) {
-                               fprintf(stderr, "Type must be a string\n");
-                               goto fail;
-                       }
-                       type = ec_node_type_lookup(value_str);
-                       if (type == NULL) {
-                               fprintf(stderr, "Cannot find type %s\n",
-                                       value_str);
-                               goto fail;
-                       }
-               } else if (!strcmp(key_str, "attrs")) {
-                       if (attrs != NULL) {
-                               fprintf(stderr, "Duplicate attrs\n");
-                               goto fail;
-                       }
-                       if (value->type != YAML_MAPPING_NODE) {
-                               fprintf(stderr, "Attrs must be a maping\n");
-                               goto fail;
-                       }
-                       attrs = value;
-               } else if (!strcmp(key_str, "id")) {
-                       if (id != NULL) {
-                               fprintf(stderr, "Duplicate id\n");
-                               goto fail;
-                       }
-                       if (value->type != YAML_SCALAR_NODE) {
-                               fprintf(stderr, "Id must be a scalar\n");
-                               goto fail;
-                       }
-                       id = value_str;
-               } else if (!strcmp(key_str, "help")) {
-                       if (help != NULL) {
-                               fprintf(stderr, "Duplicate help\n");
-                               goto fail;
-                       }
-                       if (value->type != YAML_SCALAR_NODE) {
-                               fprintf(stderr, "Help must be a scalar\n");
-                               goto fail;
-                       }
-                       help = value_str;
-               }
-       }
-
-       /* create the ecoli node */
-       if (id == NULL)
-               id = EC_NO_ID;
-       enode = ec_node_from_type(type, id);
-       if (enode == NULL) {
-               fprintf(stderr, "Cannot create ecoli node\n");
-               goto fail;
-       }
-       if (add_in_table(tree, ynode, enode) < 0) {
-               fprintf(stderr, "Cannot add node in table\n");
-               goto fail;
-       }
-       if (tree->root == NULL) {
-               ec_node_clone(enode);
-               tree->root = enode;
-       }
-
-       /* create its config */
-       schema = ec_node_type_schema(type);
-       if (schema == NULL) {
-               fprintf(stderr, "No configuration schema for type %s\n",
-                       ec_node_type_name(type));
-               goto fail;
-       }
-
-       config = parse_ec_config_dict(tree, schema, document, ynode);
-       if (config == NULL)
-               goto fail;
-
-       if (ec_node_set_config(enode, config) < 0) {
-               fprintf(stderr, "Failed to set config\n");
-               goto fail;
-       }
-
-       /* add attributes (all as string) */
-       //XXX
-
-       return enode;
-
-fail:
-       ec_node_free(enode);
-       ec_config_free(config);
-       return NULL;
-}
-
-static int
-parse_document(struct enode_tree *tree, const yaml_document_t *document)
-{
-       yaml_node_t *node;
-
-       node = document->nodes.start;
-       if (parse_ec_node(tree, document, node) == NULL)
-               return -1;
-
-       return 0;
-}
-
-static int parse_file(struct enode_tree *tree, const char *filename)
-{
-        FILE *file;
-        yaml_parser_t parser;
-        yaml_document_t document;
-
-        file = fopen(filename, "rb");
-       if (file == NULL) {
-               fprintf(stderr, "Failed to open file %s\n", filename);
-               goto fail_no_doc;
-       }
-
-        if (yaml_parser_initialize(&parser) == 0) {
-               fprintf(stderr, "Failed to initialize yaml parser\n");
-               goto fail_no_doc;
-       }
-
-        yaml_parser_set_input_file(&parser, file);
-
-       if (yaml_parser_load(&parser, &document) == 0) {
-               fprintf(stderr, "Failed to load yaml document\n");
-               goto fail_no_doc;
-       }
-
-       if (yaml_document_get_root_node(&document) == NULL) {
-               fprintf(stderr, "Incomplete document\n"); //XXX check err
-               goto fail;
-       }
-
-       if (parse_document(tree, &document) < 0) {
-               fprintf(stderr, "Failed to parse document\n");
-               goto fail;
-       }
-
-       yaml_document_delete(&document);
-        yaml_parser_delete(&parser);
-        fclose(file);
-
-       return 0;
-
-fail:
-       yaml_document_delete(&document);
-fail_no_doc:
-        yaml_parser_delete(&parser);
-        if (file != NULL)
-               fclose(file);
-
-       return -1;
-}
-
-int
-main(int argc, char *argv[])
-{
-       struct enode_tree tree;
-
-       memset(&tree, 0, sizeof(tree));
-
-       if (argc != 2) {
-               fprintf(stderr, "Invalid args\n");
-               goto fail;
-       }
-       if (parse_file(&tree, argv[1]) < 0) {
-               fprintf(stderr, "Failed to parse file\n");
-               goto fail;
-       }
-       printf("root=%p len=%zd\n", tree.root, tree.table_len);
-       ec_node_dump(stdout, tree.root);
-       free_tree(&tree);
-
-       return 0;
-
-fail:
-       free_tree(&tree);
-       return 1;
-}
diff --git a/lib/test.sh b/lib/test.sh
deleted file mode 100755 (executable)
index 83a7538..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
-
-set -e
-
-SEED=100
-while [ ${SEED} -gt 0 ]; do
-       CMD="./build/test --random-alloc-fail=1 --seed=${SEED} $*"
-       ${CMD} --log-level=0 || (
-               echo "=== test failed, replay seed=${SEED} with logs ===" &&
-               ${CMD} --log-level=6 ||
-               echo "=== test failed: ${CMD}" &&
-               false
-       )
-
-       SEED=$((SEED-1)) && continue
-done
diff --git a/lib/todo.txt b/lib/todo.txt
deleted file mode 100644 (file)
index 4e5d94f..0000000
+++ /dev/null
@@ -1,421 +0,0 @@
-tk_cmd
-======
-
-X evaluate expression tree in ec_tk_expr
-X cmd token
-- example
-X tk_re
-
-cleanup / rework
-================
-
-X ec_completed_item_update()
-X ec_completed_item_set_display_value()
-X add_no_match
-X add_partial_match
-- check XXX in code
-X properly manage quotes in shlex
-X remove the _new() functions
-X iterate children nodes without chaining them
-- add a node vector type: will be used in several nodes (ex: or, seq, ...)
-- check allocation model everywhere
-- checkpatch?
-- use linux style (update .emacs)
-- better logs
-- check return values (-1 or NULL) + use errno
-- check missing static / const
-- license: SPDX
-- check all completion nodes
-X split ecoli_tk.h
-- size_t or unsigned int?
-X rename:
-  X ec_tk -> ec_node
-  X ec_parsed_tk -> ec_parsed
-  X ec_completed_tk -> ec_completed
-  X tk, gen_tk, token, ... -> node
-  X tokens -> input_str / input_strvec ?
-X save node path in completion to fix help string
-- code coverage
-- try to hide structures
-- anything better than weakref?
-- add ec_node_defaults.[ch] providing usual implementations of node methods
-X use vec for strvec
-- ELOOP in case of loop
-- remove weakref?
-- sh_lex to provide offsets in attributes
-- accessors for all structs
-
-dependencies
-============
-
-X pass the current parsed state when parsing/completing
-X new node "once"
-- new node "condition"
-
-logs
-====
-
-X register log types
-
-yaml
-====
-
-X register nodes by name
-- interface to add attributes: all nodes must be configurable through a
-  generic api
-  - attr string
-  - attr string list
-  - attr node
-  - attr node list
-  - attr int
-
-- yaml interface to create nodes
-- example
-
-examples
-========
-
-- example which parses arguments (argc/argv)
-- example that acts as bash completion (ip link ?)
-- calculator example (var assignation, expression evaluation)
-- example with libedit
-- mini script language
-- configuration file
-- mini shell: cd, ls, cat, stat
-- mini network console based on ip
-
-doc
-===
-
-- overview
-- add api doc in .h
-- generate automatic api doc
-- architecture
-- coding rules, process
-- each node
-- allocation model
-- say that it stops at first match (no ambigous support)
-- say that completion must be exhaustive
-
-build framework
-===============
-
-- .map files for API
-- split libs, tests and examples
-- add make help
-- add make config
-- -fvisibility=
-
-tests
-=====
-
-- complete automatic tests with "make test"
-
-new nodes
-=========
-
-- regexp
-- node which always matches
-- file + partial completion
-- ether, ip, network
-- fusion node: need to match several children, same for completion
-- float
-- not
-
-encoding
-========
-
-- support utf-8 and other encodings
-- example
-- documentation
-
-netconf example
-===============
-
-- demonstration example that parses yang file and generate cli
-
-
-
------------------------
-
-readline:
-
-[tab]  list possible completions (matches/partial only)
-[?]    list what is expected, example:
-
-"command [foo] toto|titi|<int>"
-
-help("command f") ->
-  foo     (help of foo)
-  toto    (help of toto)
-  titi    (help of titi)
-  <int>   (help of int)
-
-
-----------------
-
-struct names
-============
-
-ideas:
-
-- ec_node: a node that can be parsed/completed
-- ec_parse: a tree describing the result of parse(node, input)
-- ec_comp: a list describing the result of complete(node, input)
-
-ec_comp_item
-
-
----------------
-
-node tree
-=========
-
-Example:
-
-1  seq
-2    option
-3      str(foo)
-4    or
-5      int(1,10)
-6      str(bar)
-7      str(foo)
-
-parse() returns a tree
-=======
-
-- each node of the tree refers to a ec_node
-- each node points to the strvec that matches
-- parse returns the first matching solution
-- usually try to match as many str in the vecs (seq node)
-
-[foo] ->
-1 seq
-2   option
-4   or
-7     str(foo)
-
-The parse cb of the node is:
-
-parse_cb(node, current_parse_state, strvec, *nmatch)
-
-return values:
-- 0: success, child->strvec is set by node (NULL = no_match)
-- -1: error (errno is set)
-maybe complex to use:
-- the node must set the match (ex: "return ec_parsed_node_match()")
-- the caller must use accessor to check if it matches or not
-
-alternative idea for return values:
-- >= 0: match, ret == nb_tk
-- -1: error (errno is set)
-- -2 or MAX_INT: success, but no match
-This is strange to have a specific value for no match
-With MAX_INT, this is the best (less bad) alternative
-
-alternative idea for return values:
-- ec_parse_result_match(n_tokens >= 0)
-- ec_parse_result_nomatch()
-- ec_parse_result_error(errno)
-
-A node always try to consume the maximum number of tokens.
-Example:
-1  seq
-2    option
-3      str(foo)
-4    str(foo)
-5    str(bar)
-
-[foo, foo, bar] matches
-[foo, bar] does *not* match
-
-complete() returns a list of possible completions
-==========
-
-problems:
-- partial completion: in a path dir/file, completion stops once
-  after the directory
-- displayed value is not the completion token: when completing a
-  file in several subdirectories, the full path is not displayed
-- any parent node can modify the completions, ex: add missing quotes
-  in ec_node_sh_lex(), filter completions in case of a ec_node_filter()
-- a command line may want to display the help from the most specific
-  token, or not.
-- some specific nodes can complete several tokens
-
-struct item {
-  const char *str;
-  type: full, partial, unknown
-}
-
-full: the completion item matches token
-partial: beginning of a completion, does not match the token
-         (good example is a directory in a path)
-unknown: could complete, but the node does not know how
-
-struct completion_item {
-  const char *value;
-  const char *disp;
-}
-
-struct completed_elt {
-  ec_parsed *parse_tree; // current tree state
-  ec_node *last;         // last node of the tree
-  list of items;         // list of items for this parse tree
-}
-
-struct completed {
-  list(elt)
-}
-
-The callback is:
-
-complete_cb(node, current_complete_state, current_parse_state, strvec)
-return:
-- 0 = success, the current complete state is updated
-- -1 = error (set errno?)
-
-
-a node can filter the completions
-
-
-[] ->
-  foo   3 str(foo)
-    seq
-      option
-        str(foo) <-
-
-  ""    5 int(1,10)
-    seq
-      option
-      or
-        int <-
-
-  bar   6 str(bar)
-  foo   7 str(bar)
-...
-
-
-[foo, ] ->
-
-  ?       5 int(1,10)
-  seq
-    option
-      str(foo)
-    or
-      int <-
-
-  bar   6 str(bar)
-  foo   7 str(bar)
-
-
-
------
-
-changes:
-- a completion item should contain a strvec for the value
-  (the display string remains a string)
-- there is maybe no good reason to split in:
-  - ec_completed_item()
-  - ec_completed_item_set()
-  - ec_completed_item_set_display()
-  - ec_completed_item_add()
-
------
-
-sh_lex
-  or
-    str(foo)
-    str(foo2)
-    str(bar)
-
-complete(sh_lex, ["'fo"])
-  complete(sh_lex, ["fo"]) -> ["foo", "foo2"]
-  
-
------
-
-#include <stdio.h>
-#include <stdbool.h>
-
-
-struct res {
-       int a;
-};
-
-static inline bool is_success(struct res r)
-{
-       if (r.a == 0)
-               return true;
-       return false;
-}
-
-
-static inline struct res res(int a)
-{
-       struct res r;
-       r.a = a;
-       return r;
-}
-
-int main(void)
-{
-       struct res r;
-
-       r = res(0);
-
-       printf("%d\n", r.a);
-       if (is_success(r))
-               printf("success: %d\n", r.a);
-
-       r = res(1);
-
-       printf("%d\n", r.a);
-       if (is_success(r))
-               printf("success: %d\n", r.a);
-
-       return 0;
-}
-
-
-----
-
-
-expr expr expr
-
-[toto] | tutu
-
-[toto [titi]]
-
-
-
-pre_op = "!"
-post_op = "^"
-post = val |
-       pre_op expr |
-       "(" expr ")"
-term = post post_op*
-prod = term ( "*" term )*
-sum = prod ( "+" prod )*
-expr = sum
-
-
------
-
-break on malloc:
-
-b debug_malloc
-# or: b debug_realloc
-condition <breakoint num> malloc_seq >= <value>
-
-alternative
-
-watch malloc_seq
-condition <watchpoint num> malloc_seq == <value + 1>
-run <args...>
-c
-
-
----------------
-
-
diff --git a/libecoli/ecoli_assert.c b/libecoli/ecoli_assert.c
new file mode 100644 (file)
index 0000000..a36d6f6
--- /dev/null
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <ecoli_assert.h>
+
+void __ec_assert_print(bool expr, const char *expr_str, const char *format, ...)
+{
+       va_list ap;
+
+       if (expr)
+               return;
+
+       /* LCOV_EXCL_START */
+       va_start(ap, format);
+       fprintf(stderr, "assertion failed: '%s' is false\n", expr_str);
+       vfprintf(stderr, format, ap);
+       va_end(ap);
+       abort();
+       /* LCOV_EXCL_END */
+}
diff --git a/libecoli/ecoli_assert.h b/libecoli/ecoli_assert.h
new file mode 100644 (file)
index 0000000..fcd2186
--- /dev/null
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Assert API
+ *
+ * Helpers to check at runtime if a condition is true, or otherwise
+ * either abort (exit program) or return an error.
+ */
+
+#ifndef ECOLI_ASSERT_
+#define ECOLI_ASSERT_
+
+#include <stdbool.h>
+
+/**
+ * Abort if the condition is false.
+ *
+ * If expression is false this macro will prints an error message to
+ * standard error and terminates the program by calling abort(3).
+ *
+ * @param expr
+ *   The expression to be checked.
+ * @param args
+ *   The format string, optionally followed by other arguments.
+ */
+#define ec_assert_print(expr, args...) \
+       __ec_assert_print(expr, #expr, args)
+
+/* internal */
+void __ec_assert_print(bool expr, const char *expr_str,
+               const char *format, ...);
+
+/**
+ * Check a condition or return.
+ *
+ * If the condition is true, do nothing. If it is false, set
+ * errno and return the specified value.
+ *
+ * @param cond
+ *   The condition to test.
+ * @param ret
+ *   The value to return.
+ * @param err
+ *   The errno to set.
+ */
+#define EC_CHECK_ARG(cond, ret, err) do {                              \
+               if (!(cond)) {                                          \
+                       errno = err;                                    \
+                       return ret;                                     \
+               }                                                       \
+       } while(0)
+
+#endif
diff --git a/libecoli/ecoli_complete.c b/libecoli/ecoli_complete.c
new file mode 100644 (file)
index 0000000..7ad846c
--- /dev/null
@@ -0,0 +1,765 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_node_sh_lex.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_or.h>
+#include <ecoli_complete.h>
+
+EC_LOG_TYPE_REGISTER(comp);
+
+struct ec_comp_item {
+       TAILQ_ENTRY(ec_comp_item) next;
+       enum ec_comp_type type;
+       struct ec_comp_group *grp;
+       char *start;      /* the initial token */
+       char *full;       /* the full token after completion */
+       char *completion; /* chars that are added, NULL if not applicable */
+       char *display;    /* what should be displayed by help/completers */
+       struct ec_keyval *attrs;
+};
+
+struct ec_comp *ec_comp(struct ec_parse *state)
+{
+       struct ec_comp *comp = NULL;
+
+       comp = ec_calloc(1, sizeof(*comp));
+       if (comp == NULL)
+               goto fail;
+
+       comp->attrs = ec_keyval();
+       if (comp->attrs == NULL)
+               goto fail;
+
+       TAILQ_INIT(&comp->groups);
+
+       comp->cur_state = state;
+
+       return comp;
+
+ fail:
+       if (comp != NULL)
+               ec_keyval_free(comp->attrs);
+       ec_free(comp);
+
+       return NULL;
+}
+
+struct ec_parse *ec_comp_get_state(struct ec_comp *comp)
+{
+       return comp->cur_state;
+}
+
+int
+ec_node_complete_child(const struct ec_node *node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_parse *child_state, *cur_state;
+       struct ec_comp_group *cur_group;
+       int ret;
+
+       if (ec_node_type(node)->complete == NULL) {
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       /* save previous parse state, prepare child state */
+       cur_state = comp->cur_state;
+       child_state = ec_parse(node);
+       if (child_state == NULL)
+               return -1;
+
+       if (cur_state != NULL)
+               ec_parse_link_child(cur_state, child_state);
+       comp->cur_state = child_state;
+       cur_group = comp->cur_group;
+       comp->cur_group = NULL;
+
+       /* fill the comp struct with items */
+       ret = ec_node_type(node)->complete(node, comp, strvec);
+
+       /* restore parent parse state */
+       if (cur_state != NULL) {
+               ec_parse_unlink_child(cur_state, child_state);
+               assert(!ec_parse_has_child(child_state));
+       }
+       ec_parse_free(child_state);
+       comp->cur_state = cur_state;
+       comp->cur_group = cur_group;
+
+       if (ret < 0)
+               return -1;
+
+       return 0;
+}
+
+struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
+       const struct ec_strvec *strvec)
+{
+       struct ec_comp *comp = NULL;
+       int ret;
+
+       comp = ec_comp(NULL);
+       if (comp == NULL)
+               goto fail;
+
+       ret = ec_node_complete_child(node, comp, strvec);
+       if (ret < 0)
+               goto fail;
+
+       return comp;
+
+fail:
+       ec_comp_free(comp);
+       return NULL;
+}
+
+struct ec_comp *ec_node_complete(const struct ec_node *node,
+       const char *str)
+{
+       struct ec_strvec *strvec = NULL;
+       struct ec_comp *comp;
+
+       errno = ENOMEM;
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       if (ec_strvec_add(strvec, str) < 0)
+               goto fail;
+
+       comp = ec_node_complete_strvec(node, strvec);
+       if (comp == NULL)
+               goto fail;
+
+       ec_strvec_free(strvec);
+       return comp;
+
+ fail:
+       ec_strvec_free(strvec);
+       return NULL;
+}
+
+static struct ec_comp_group *
+ec_comp_group(const struct ec_node *node, struct ec_parse *parse)
+{
+       struct ec_comp_group *grp = NULL;
+
+       grp = ec_calloc(1, sizeof(*grp));
+       if (grp == NULL)
+               return NULL;
+
+       grp->attrs = ec_keyval();
+       if (grp->attrs == NULL)
+               goto fail;
+
+       grp->state = ec_parse_dup(parse);
+       if (grp->state == NULL)
+               goto fail;
+
+       grp->node = node;
+       TAILQ_INIT(&grp->items);
+
+       return grp;
+
+fail:
+       if (grp != NULL) {
+               ec_parse_free(grp->state);
+               ec_keyval_free(grp->attrs);
+       }
+       ec_free(grp);
+       return NULL;
+}
+
+static struct ec_comp_item *
+ec_comp_item(enum ec_comp_type type,
+       const char *start, const char *full)
+{
+       struct ec_comp_item *item = NULL;
+       struct ec_keyval *attrs = NULL;
+       char *comp_cp = NULL, *start_cp = NULL;
+       char *full_cp = NULL, *display_cp = NULL;
+
+       if (type == EC_COMP_UNKNOWN && full != NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+       if (type != EC_COMP_UNKNOWN && full == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       item = ec_calloc(1, sizeof(*item));
+       if (item == NULL)
+               goto fail;
+
+       attrs = ec_keyval();
+       if (attrs == NULL)
+               goto fail;
+
+       if (start != NULL) {
+               start_cp = ec_strdup(start);
+               if (start_cp == NULL)
+                       goto fail;
+
+               if (ec_str_startswith(full, start)) {
+                       comp_cp = ec_strdup(&full[strlen(start)]);
+                       if (comp_cp == NULL)
+                               goto fail;
+               }
+       }
+       if (full != NULL) {
+               full_cp = ec_strdup(full);
+               if (full_cp == NULL)
+                       goto fail;
+               display_cp = ec_strdup(full);
+               if (display_cp == NULL)
+                       goto fail;
+       }
+
+       item->type = type;
+       item->start = start_cp;
+       item->full = full_cp;
+       item->completion = comp_cp;
+       item->display = display_cp;
+       item->attrs = attrs;
+
+       return item;
+
+fail:
+       ec_keyval_free(attrs);
+       ec_free(comp_cp);
+       ec_free(start_cp);
+       ec_free(full_cp);
+       ec_free(display_cp);
+       ec_free(item);
+
+       return NULL;
+}
+
+int ec_comp_item_set_display(struct ec_comp_item *item,
+                               const char *display)
+{
+       char *display_copy = NULL;
+
+       if (item == NULL || display == NULL ||
+                       item->type == EC_COMP_UNKNOWN) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       display_copy = ec_strdup(display);
+       if (display_copy == NULL)
+               goto fail;
+
+       ec_free(item->display);
+       item->display = display_copy;
+
+       return 0;
+
+fail:
+       ec_free(display_copy);
+       return -1;
+}
+
+int
+ec_comp_item_set_completion(struct ec_comp_item *item,
+                               const char *completion)
+{
+       char *completion_copy = NULL;
+
+       if (item == NULL || completion == NULL ||
+                       item->type == EC_COMP_UNKNOWN) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       completion_copy = ec_strdup(completion);
+       if (completion_copy == NULL)
+               goto fail;
+
+       ec_free(item->completion);
+       item->completion = completion_copy;
+
+       return 0;
+
+fail:
+       ec_free(completion_copy);
+       return -1;
+}
+
+int
+ec_comp_item_set_str(struct ec_comp_item *item,
+                       const char *str)
+{
+       char *str_copy = NULL;
+
+       if (item == NULL || str == NULL ||
+                       item->type == EC_COMP_UNKNOWN) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       str_copy = ec_strdup(str);
+       if (str_copy == NULL)
+               goto fail;
+
+       ec_free(item->full);
+       item->full = str_copy;
+
+       return 0;
+
+fail:
+       ec_free(str_copy);
+       return -1;
+}
+
+static int
+ec_comp_item_add(struct ec_comp *comp, const struct ec_node *node,
+               struct ec_comp_item *item)
+{
+       if (comp == NULL || item == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       switch (item->type) {
+       case EC_COMP_UNKNOWN:
+               comp->count_unknown++;
+               break;
+       case EC_COMP_FULL:
+               comp->count_full++;
+               break;
+       case EC_COMP_PARTIAL:
+               comp->count_partial++;
+               break;
+       default:
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (comp->cur_group == NULL) {
+               struct ec_comp_group *grp;
+
+               grp = ec_comp_group(node, comp->cur_state);
+               if (grp == NULL)
+                       return -1;
+               TAILQ_INSERT_TAIL(&comp->groups, grp, next);
+               comp->cur_group = grp;
+       }
+
+       comp->count++;
+       TAILQ_INSERT_TAIL(&comp->cur_group->items, item, next);
+       item->grp = comp->cur_group;
+
+       return 0;
+}
+
+const char *
+ec_comp_item_get_str(const struct ec_comp_item *item)
+{
+       return item->full;
+}
+
+const char *
+ec_comp_item_get_display(const struct ec_comp_item *item)
+{
+       return item->display;
+}
+
+const char *
+ec_comp_item_get_completion(const struct ec_comp_item *item)
+{
+       return item->completion;
+}
+
+enum ec_comp_type
+ec_comp_item_get_type(const struct ec_comp_item *item)
+{
+       return item->type;
+}
+
+const struct ec_comp_group *
+ec_comp_item_get_grp(const struct ec_comp_item *item)
+{
+       return item->grp;
+}
+
+const struct ec_node *
+ec_comp_item_get_node(const struct ec_comp_item *item)
+{
+       return ec_comp_item_get_grp(item)->node;
+}
+
+static void
+ec_comp_item_free(struct ec_comp_item *item)
+{
+       if (item == NULL)
+               return;
+
+       ec_free(item->full);
+       ec_free(item->start);
+       ec_free(item->completion);
+       ec_free(item->display);
+       ec_keyval_free(item->attrs);
+       ec_free(item);
+}
+
+int ec_comp_add_item(struct ec_comp *comp,
+                       const struct ec_node *node,
+                       struct ec_comp_item **p_item,
+                       enum ec_comp_type type,
+                       const char *start, const char *full)
+{
+       struct ec_comp_item *item = NULL;
+       int ret;
+
+       item = ec_comp_item(type, start, full);
+       if (item == NULL)
+               return -1;
+
+       ret = ec_comp_item_add(comp, node, item);
+       if (ret < 0)
+               goto fail;
+
+       if (p_item != NULL)
+               *p_item = item;
+
+       return 0;
+
+fail:
+       ec_comp_item_free(item);
+
+       return -1;
+}
+
+/* return a completion item of type "unknown" */
+int
+ec_node_complete_unknown(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       int ret;
+
+       if (ec_strvec_len(strvec) != 1)
+               return 0;
+
+       ret = ec_comp_add_item(comp, gen_node, NULL,
+                               EC_COMP_UNKNOWN, NULL, NULL);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static void ec_comp_group_free(struct ec_comp_group *grp)
+{
+       struct ec_comp_item *item;
+
+       if (grp == NULL)
+               return;
+
+       while (!TAILQ_EMPTY(&grp->items)) {
+               item = TAILQ_FIRST(&grp->items);
+               TAILQ_REMOVE(&grp->items, item, next);
+               ec_comp_item_free(item);
+       }
+       ec_parse_free(ec_parse_get_root(grp->state));
+       ec_keyval_free(grp->attrs);
+       ec_free(grp);
+}
+
+void ec_comp_free(struct ec_comp *comp)
+{
+       struct ec_comp_group *grp;
+
+       if (comp == NULL)
+               return;
+
+       while (!TAILQ_EMPTY(&comp->groups)) {
+               grp = TAILQ_FIRST(&comp->groups);
+               TAILQ_REMOVE(&comp->groups, grp, next);
+               ec_comp_group_free(grp);
+       }
+       ec_keyval_free(comp->attrs);
+       ec_free(comp);
+}
+
+void ec_comp_dump(FILE *out, const struct ec_comp *comp)
+{
+       struct ec_comp_group *grp;
+       struct ec_comp_item *item;
+
+       if (comp == NULL || comp->count == 0) {
+               fprintf(out, "no completion\n");
+               return;
+       }
+
+       fprintf(out, "completion: count=%u full=%u partial=%u unknown=%u\n",
+               comp->count, comp->count_full,
+               comp->count_partial,  comp->count_unknown);
+
+       TAILQ_FOREACH(grp, &comp->groups, next) {
+               fprintf(out, "node=%p, node_type=%s\n",
+                       grp->node, ec_node_type(grp->node)->name);
+               TAILQ_FOREACH(item, &grp->items, next) {
+                       const char *typestr;
+
+                       switch (item->type) {
+                       case EC_COMP_UNKNOWN: typestr = "unknown"; break;
+                       case EC_COMP_FULL: typestr = "full"; break;
+                       case EC_COMP_PARTIAL: typestr = "partial"; break;
+                       default: typestr = "unknown"; break;
+                       }
+
+                       fprintf(out, "  type=%s str=<%s> comp=<%s> disp=<%s>\n",
+                               typestr, item->full, item->completion,
+                               item->display);
+               }
+       }
+}
+
+int ec_comp_merge(struct ec_comp *to,
+               struct ec_comp *from)
+{
+       struct ec_comp_group *grp;
+
+       while (!TAILQ_EMPTY(&from->groups)) {
+               grp = TAILQ_FIRST(&from->groups);
+               TAILQ_REMOVE(&from->groups, grp, next);
+               TAILQ_INSERT_TAIL(&to->groups, grp, next);
+       }
+       to->count += from->count;
+       to->count_full += from->count_full;
+       to->count_partial += from->count_partial;
+       to->count_unknown += from->count_unknown;
+
+       ec_comp_free(from);
+       return 0;
+}
+
+unsigned int ec_comp_count(
+       const struct ec_comp *comp,
+       enum ec_comp_type type)
+{
+       unsigned int count = 0;
+
+       if (comp == NULL)
+               return count;
+
+       if (type & EC_COMP_FULL)
+               count += comp->count_full;
+       if (type & EC_COMP_PARTIAL)
+               count += comp->count_partial;
+       if (type & EC_COMP_UNKNOWN)
+               count += comp->count_unknown;
+
+       return count;
+}
+
+struct ec_comp_iter *
+ec_comp_iter(struct ec_comp *comp,
+       enum ec_comp_type type)
+{
+       struct ec_comp_iter *iter;
+
+       iter = ec_calloc(1, sizeof(*iter));
+       if (iter == NULL)
+               return NULL;
+
+       iter->comp = comp;
+       iter->type = type;
+       iter->cur_node = NULL;
+       iter->cur_match = NULL;
+
+       return iter;
+}
+
+struct ec_comp_item *ec_comp_iter_next(
+       struct ec_comp_iter *iter)
+{
+       struct ec_comp *comp;
+       struct ec_comp_group *cur_node;
+       struct ec_comp_item *cur_match;
+
+       if (iter == NULL)
+               return NULL;
+       comp = iter->comp;
+       if (comp == NULL)
+               return NULL;
+
+       cur_node = iter->cur_node;
+       cur_match = iter->cur_match;
+
+       /* first call */
+       if (cur_node == NULL) {
+               TAILQ_FOREACH(cur_node, &comp->groups, next) {
+                       TAILQ_FOREACH(cur_match, &cur_node->items, next) {
+                               if (cur_match != NULL &&
+                                               cur_match->type & iter->type)
+                                       goto found;
+                       }
+               }
+               return NULL;
+       } else {
+               cur_match = TAILQ_NEXT(cur_match, next);
+               if (cur_match != NULL &&
+                               cur_match->type & iter->type)
+                       goto found;
+               cur_node = TAILQ_NEXT(cur_node, next);
+               while (cur_node != NULL) {
+                       cur_match = TAILQ_FIRST(&cur_node->items);
+                       if (cur_match != NULL &&
+                                       cur_match->type & iter->type)
+                               goto found;
+                       cur_node = TAILQ_NEXT(cur_node, next);
+               }
+               return NULL;
+       }
+
+found:
+       iter->cur_node = cur_node;
+       iter->cur_match = cur_match;
+
+       return iter->cur_match;
+}
+
+void ec_comp_iter_free(struct ec_comp_iter *iter)
+{
+       ec_free(iter);
+}
+
+/* LCOV_EXCL_START */
+static int ec_comp_testcase(void)
+{
+       struct ec_node *node = NULL;
+       struct ec_comp *c = NULL;
+       struct ec_comp_iter *iter = NULL;
+       struct ec_comp_item *item;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+       int testres = 0;
+
+       node = ec_node_sh_lex(EC_NO_ID,
+                       EC_NODE_OR(EC_NO_ID,
+                               ec_node_str("id_x", "xx"),
+                               ec_node_str("id_y", "yy")));
+       if (node == NULL)
+               goto fail;
+
+       c = ec_node_complete(node, "xcdscds");
+       testres |= EC_TEST_CHECK(
+               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 0,
+               "complete count should is not 0\n");
+       ec_comp_free(c);
+
+       c = ec_node_complete(node, "x");
+       testres |= EC_TEST_CHECK(
+               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 1,
+               "complete count should is not 1\n");
+       ec_comp_free(c);
+
+       c = ec_node_complete(node, "");
+       testres |= EC_TEST_CHECK(
+               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 2,
+               "complete count should is not 2\n");
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_comp_dump(f, NULL);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "no completion"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_comp_dump(f, c);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "comp=<xx>"), "bad dump\n");
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "comp=<yy>"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       iter = ec_comp_iter(c, EC_COMP_ALL);
+       item = ec_comp_iter_next(iter);
+       if (item == NULL)
+               goto fail;
+
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_comp_item_get_display(item), "xx"),
+               "bad item display\n");
+       testres |= EC_TEST_CHECK(
+               ec_comp_item_get_type(item) == EC_COMP_FULL,
+               "bad item type\n");
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_x"),
+               "bad item node\n");
+
+       item = ec_comp_iter_next(iter);
+       if (item == NULL)
+               goto fail;
+
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_comp_item_get_display(item), "yy"),
+               "bad item display\n");
+       testres |= EC_TEST_CHECK(
+               ec_comp_item_get_type(item) == EC_COMP_FULL,
+               "bad item type\n");
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_y"),
+               "bad item node\n");
+
+       item = ec_comp_iter_next(iter);
+       testres |= EC_TEST_CHECK(item == NULL, "should be the last item\n");
+
+       ec_comp_iter_free(iter);
+       ec_comp_free(c);
+       ec_node_free(node);
+
+       return testres;
+
+fail:
+       ec_comp_iter_free(iter);
+       ec_comp_free(c);
+       ec_node_free(node);
+       if (f != NULL)
+               fclose(f);
+       free(buf);
+
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_comp_test = {
+       .name = "comp",
+       .test = ec_comp_testcase,
+};
+
+EC_TEST_REGISTER(ec_comp_test);
diff --git a/libecoli/ecoli_complete.h b/libecoli/ecoli_complete.h
new file mode 100644 (file)
index 0000000..1ed67f0
--- /dev/null
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * API for generating completions item on a node.
+ *
+ * This file provide helpers to list and manipulate the possible
+ * completions for a given input.
+ *
+ * XXX comp vs item
+ */
+
+#ifndef ECOLI_COMPLETE_
+#define ECOLI_COMPLETE_
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+struct ec_node;
+
+enum ec_comp_type { /* XXX should be a define */
+       EC_COMP_UNKNOWN = 0x1,
+       EC_COMP_FULL = 0x2,
+       EC_COMP_PARTIAL = 0x4,
+       EC_COMP_ALL = 0x7,
+};
+
+struct ec_comp_item;
+
+TAILQ_HEAD(ec_comp_item_list, ec_comp_item);
+
+struct ec_comp_group {
+       TAILQ_ENTRY(ec_comp_group) next;
+       const struct ec_node *node;
+       struct ec_comp_item_list items;
+       struct ec_parse *state;
+       struct ec_keyval *attrs;
+};
+
+TAILQ_HEAD(ec_comp_group_list, ec_comp_group);
+
+struct ec_comp {
+       unsigned count;
+       unsigned count_full;
+       unsigned count_partial;
+       unsigned count_unknown;
+       struct ec_parse *cur_state;
+       struct ec_comp_group *cur_group;
+       struct ec_comp_group_list groups;
+       struct ec_keyval *attrs;
+};
+
+/*
+ * return a comp object filled with items
+ * return NULL on error (nomem?)
+ */
+struct ec_comp *ec_node_complete(const struct ec_node *node,
+       const char *str);
+struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
+       const struct ec_strvec *strvec);
+
+/* internal: used by nodes */
+int ec_node_complete_child(const struct ec_node *node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec);
+
+/**
+ * Create a completion object (list of completion items).
+ *
+ *
+ */
+struct ec_comp *ec_comp(struct ec_parse *state);
+
+/**
+ * Free a completion object and all its items.
+ *
+ *
+ */
+void ec_comp_free(struct ec_comp *comp);
+
+/**
+ *
+ *
+ *
+ */
+void ec_comp_dump(FILE *out,
+       const struct ec_comp *comp);
+
+/**
+ * Merge items contained in 'from' into 'to'
+ *
+ * The 'from' comp struct is freed.
+ */
+int ec_comp_merge(struct ec_comp *to,
+               struct ec_comp *from);
+
+struct ec_parse *ec_comp_get_state(struct ec_comp *comp);
+
+/* shortcut for ec_comp_item() + ec_comp_item_add() */
+int ec_comp_add_item(struct ec_comp *comp,
+                       const struct ec_node *node,
+                       struct ec_comp_item **p_item,
+                       enum ec_comp_type type,
+                       const char *start, const char *full);
+
+/**
+ *
+ */
+int ec_comp_item_set_str(struct ec_comp_item *item,
+                       const char *str);
+
+/**
+ * Get the string value of a completion item.
+ *
+ *
+ */
+const char *
+ec_comp_item_get_str(const struct ec_comp_item *item);
+
+/**
+ * Get the display string value of a completion item.
+ *
+ *
+ */
+const char *
+ec_comp_item_get_display(const struct ec_comp_item *item);
+
+/**
+ * Get the completion string value of a completion item.
+ *
+ *
+ */
+const char *
+ec_comp_item_get_completion(const struct ec_comp_item *item);
+
+/**
+ * Get the group of a completion item.
+ *
+ *
+ */
+const struct ec_comp_group *
+ec_comp_item_get_grp(const struct ec_comp_item *item);
+
+/**
+ * Get the type of a completion item.
+ *
+ *
+ */
+enum ec_comp_type
+ec_comp_item_get_type(const struct ec_comp_item *item);
+
+/**
+ * Get the node associated to a completion item.
+ *
+ *
+ */
+const struct ec_node *
+ec_comp_item_get_node(const struct ec_comp_item *item);
+
+/**
+ * Set the display value of an item.
+ *
+ *
+ */
+int ec_comp_item_set_display(struct ec_comp_item *item,
+                               const char *display);
+
+/**
+ * Set the completion value of an item.
+ *
+ *
+ */
+int ec_comp_item_set_completion(struct ec_comp_item *item,
+                               const char *completion);
+
+/**
+ *
+ *
+ *
+ */
+int
+ec_node_complete_unknown(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec);
+
+/**
+ *
+ *
+ *
+ */
+unsigned int ec_comp_count(
+       const struct ec_comp *comp,
+       enum ec_comp_type flags);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_comp_iter {
+       enum ec_comp_type type;
+       struct ec_comp *comp;
+       struct ec_comp_group *cur_node;
+       struct ec_comp_item *cur_match;
+};
+
+/**
+ *
+ *
+ *
+ */
+struct ec_comp_iter *
+ec_comp_iter(struct ec_comp *comp,
+       enum ec_comp_type type);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_comp_item *ec_comp_iter_next(
+       struct ec_comp_iter *iter);
+
+/**
+ *
+ *
+ *
+ */
+void ec_comp_iter_free(struct ec_comp_iter *iter);
+
+
+#endif
diff --git a/libecoli/ecoli_config.c b/libecoli/ecoli_config.c
new file mode 100644 (file)
index 0000000..0698f07
--- /dev/null
@@ -0,0 +1,1153 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/queue.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <ecoli_string.h>
+#include <ecoli_malloc.h>
+#include <ecoli_keyval.h>
+#include <ecoli_node.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_config.h>
+
+EC_LOG_TYPE_REGISTER(config);
+
+const char *ec_config_reserved_keys[] = {
+       "id",
+       "attrs",
+       "help",
+       "type",
+};
+
+static int
+__ec_config_dump(FILE *out, const char *key, const struct ec_config *config,
+       size_t indent);
+static int
+ec_config_dict_validate(const struct ec_keyval *dict,
+                       const struct ec_config_schema *schema);
+
+bool
+ec_config_key_is_reserved(const char *name)
+{
+       size_t i;
+
+       for (i = 0; i < EC_COUNT_OF(ec_config_reserved_keys); i++) {
+               if (!strcmp(name, ec_config_reserved_keys[i]))
+                       return true;
+       }
+       return false;
+}
+
+/* return ec_value type as a string */
+static const char *
+ec_config_type_str(enum ec_config_type type)
+{
+       switch (type) {
+       case EC_CONFIG_TYPE_BOOL: return "bool";
+       case EC_CONFIG_TYPE_INT64: return "int64";
+       case EC_CONFIG_TYPE_UINT64: return "uint64";
+       case EC_CONFIG_TYPE_STRING: return "string";
+       case EC_CONFIG_TYPE_NODE: return "node";
+       case EC_CONFIG_TYPE_LIST: return "list";
+       case EC_CONFIG_TYPE_DICT: return "dict";
+       default: return "unknown";
+       }
+}
+
+static size_t
+ec_config_schema_len(const struct ec_config_schema *schema)
+{
+       size_t i;
+
+       if (schema == NULL)
+               return 0;
+       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++)
+               ;
+       return i;
+}
+
+static int
+__ec_config_schema_validate(const struct ec_config_schema *schema,
+                       enum ec_config_type type)
+{
+       size_t i, j;
+       int ret;
+
+       if (type == EC_CONFIG_TYPE_LIST) {
+               if (schema[0].key != NULL) {
+                       errno = EINVAL;
+                       EC_LOG(EC_LOG_ERR, "list schema key must be NULL\n");
+                       return -1;
+               }
+       } else if (type == EC_CONFIG_TYPE_DICT) {
+               for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
+                       if (schema[i].key == NULL) {
+                               errno = EINVAL;
+                               EC_LOG(EC_LOG_ERR,
+                                       "dict schema key should not be NULL\n");
+                               return -1;
+                       }
+               }
+       } else {
+               errno = EINVAL;
+               EC_LOG(EC_LOG_ERR, "invalid schema type\n");
+               return -1;
+       }
+
+       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
+               if (schema[i].key != NULL &&
+                               ec_config_key_is_reserved(schema[i].key)) {
+                       errno = EINVAL;
+                       EC_LOG(EC_LOG_ERR,
+                               "key name <%s> is reserved\n", schema[i].key);
+                       return -1;
+               }
+               /* check for duplicate name if more than one element */
+               for (j = i + 1; schema[j].type != EC_CONFIG_TYPE_NONE; j++) {
+                       if (!strcmp(schema[i].key, schema[j].key)) {
+                               errno = EEXIST;
+                               EC_LOG(EC_LOG_ERR,
+                                       "duplicate key <%s> in schema\n",
+                                       schema[i].key);
+                               return -1;
+                       }
+               }
+
+               switch (schema[i].type) {
+               case EC_CONFIG_TYPE_BOOL:
+               case EC_CONFIG_TYPE_INT64:
+               case EC_CONFIG_TYPE_UINT64:
+               case EC_CONFIG_TYPE_STRING:
+               case EC_CONFIG_TYPE_NODE:
+                       if (schema[i].subschema != NULL || ec_config_schema_len(
+                                       schema[i].subschema) != 0) {
+                               errno = EINVAL;
+                               EC_LOG(EC_LOG_ERR,
+                                       "key <%s> should not have subtype/subschema\n",
+                                       schema[i].key);
+                               return -1;
+                       }
+                       break;
+               case EC_CONFIG_TYPE_LIST:
+                       if (schema[i].subschema == NULL || ec_config_schema_len(
+                                       schema[i].subschema) != 1) {
+                               errno = EINVAL;
+                               EC_LOG(EC_LOG_ERR,
+                                       "key <%s> must have subschema of length 1\n",
+                                       schema[i].key);
+                               return -1;
+                       }
+                       break;
+               case EC_CONFIG_TYPE_DICT:
+                       if (schema[i].subschema == NULL || ec_config_schema_len(
+                                       schema[i].subschema) == 0) {
+                               errno = EINVAL;
+                               EC_LOG(EC_LOG_ERR,
+                                       "key <%s> must have subschema\n",
+                                       schema[i].key);
+                               return -1;
+                       }
+                       break;
+               default:
+                       EC_LOG(EC_LOG_ERR, "invalid type for key <%s>\n",
+                               schema[i].key);
+                       errno = EINVAL;
+                       return -1;
+               }
+
+               if (schema[i].subschema == NULL)
+                       continue;
+
+               ret = __ec_config_schema_validate(schema[i].subschema,
+                                               schema[i].type);
+               if (ret < 0) {
+                       EC_LOG(EC_LOG_ERR, "cannot parse subschema %s%s\n",
+                               schema[i].key ? "key=" : "",
+                               schema[i].key ? : "");
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+int
+ec_config_schema_validate(const struct ec_config_schema *schema)
+{
+       return __ec_config_schema_validate(schema, EC_CONFIG_TYPE_DICT);
+}
+
+static void
+__ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema,
+                       size_t indent)
+{
+       size_t i;
+
+       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
+               fprintf(out, "%*s" "%s%s%stype=%s desc='%s'\n",
+                       (int)indent * 4, "",
+                       schema[i].key ? "key=": "",
+                       schema[i].key ? : "",
+                       schema[i].key ? " ": "",
+                       ec_config_type_str(schema[i].type),
+                       schema[i].desc);
+               if (schema[i].subschema == NULL)
+                       continue;
+               __ec_config_schema_dump(out, schema[i].subschema,
+                                       indent + 1);
+       }
+}
+
+void
+ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema)
+{
+       fprintf(out, "------------------- schema dump:\n");
+
+       if (schema == NULL) {
+               fprintf(out, "no schema\n");
+               return;
+       }
+
+       __ec_config_schema_dump(out, schema, 0);
+}
+
+enum ec_config_type ec_config_get_type(const struct ec_config *config)
+{
+       return config->type;
+}
+
+struct ec_config *
+ec_config_bool(bool boolean)
+{
+       struct ec_config *value = NULL;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               return NULL;
+
+       value->type = EC_CONFIG_TYPE_BOOL;
+       value->boolean = boolean;
+
+       return value;
+}
+
+struct ec_config *
+ec_config_i64(int64_t i64)
+{
+       struct ec_config *value = NULL;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               return NULL;
+
+       value->type = EC_CONFIG_TYPE_INT64;
+       value->i64 = i64;
+
+       return value;
+}
+
+struct ec_config *
+ec_config_u64(uint64_t u64)
+{
+       struct ec_config *value = NULL;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               return NULL;
+
+       value->type = EC_CONFIG_TYPE_UINT64;
+       value->u64 = u64;
+
+       return value;
+}
+
+/* duplicate string */
+struct ec_config *
+ec_config_string(const char *string)
+{
+       struct ec_config *value = NULL;
+       char *s = NULL;
+
+       if (string == NULL)
+               goto fail;
+
+       s = ec_strdup(string);
+       if (s == NULL)
+               goto fail;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               goto fail;
+
+       value->type = EC_CONFIG_TYPE_STRING;
+       value->string = s;
+
+       return value;
+
+fail:
+       ec_free(value);
+       ec_free(s);
+       return NULL;
+}
+
+/* "consume" the node */
+struct ec_config *
+ec_config_node(struct ec_node *node)
+{
+       struct ec_config *value = NULL;
+
+       if (node == NULL)
+               goto fail;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               goto fail;
+
+       value->type = EC_CONFIG_TYPE_NODE;
+       value->node = node;
+
+       return value;
+
+fail:
+       ec_node_free(node);
+       ec_free(value);
+       return NULL;
+}
+
+struct ec_config *
+ec_config_dict(void)
+{
+       struct ec_config *value = NULL;
+       struct ec_keyval *dict = NULL;
+
+       dict = ec_keyval();
+       if (dict == NULL)
+               goto fail;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               goto fail;
+
+       value->type = EC_CONFIG_TYPE_DICT;
+       value->dict = dict;
+
+       return value;
+
+fail:
+       ec_keyval_free(dict);
+       ec_free(value);
+       return NULL;
+}
+
+struct ec_config *
+ec_config_list(void)
+{
+       struct ec_config *value = NULL;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               return NULL;
+
+       value->type = EC_CONFIG_TYPE_LIST;
+       TAILQ_INIT(&value->list);
+
+       return value;
+}
+
+const struct ec_config_schema *
+ec_config_schema_lookup(const struct ec_config_schema *schema,
+                       const char *key)
+{
+       size_t i;
+
+       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
+               if (!strcmp(key, schema[i].key))
+                       return &schema[i];
+       }
+
+       errno = ENOENT;
+       return NULL;
+}
+
+enum ec_config_type
+ec_config_schema_type(const struct ec_config_schema *schema_elt)
+{
+       return schema_elt->type;
+}
+
+const struct ec_config_schema *
+ec_config_schema_sub(const struct ec_config_schema *schema_elt)
+{
+       return schema_elt->subschema;
+}
+
+void
+ec_config_free(struct ec_config *value)
+{
+       if (value == NULL)
+               return;
+
+       switch (value->type) {
+       case EC_CONFIG_TYPE_STRING:
+               ec_free(value->string);
+               break;
+       case EC_CONFIG_TYPE_NODE:
+               ec_node_free(value->node);
+               break;
+       case EC_CONFIG_TYPE_LIST:
+               while (!TAILQ_EMPTY(&value->list)) {
+                       struct ec_config *v;
+                       v = TAILQ_FIRST(&value->list);
+                       TAILQ_REMOVE(&value->list, v, next);
+                       ec_config_free(v);
+               }
+               break;
+       case EC_CONFIG_TYPE_DICT:
+               ec_keyval_free(value->dict);
+               break;
+       default:
+               break;
+       }
+
+       ec_free(value);
+}
+
+static int
+ec_config_list_cmp(const struct ec_config_list *list1,
+               const struct ec_config_list *list2)
+{
+       const struct ec_config *v1, *v2;
+
+       for (v1 = TAILQ_FIRST(list1), v2 = TAILQ_FIRST(list2);
+            v1 != NULL && v2 != NULL;
+            v1 = TAILQ_NEXT(v1, next), v2 = TAILQ_NEXT(v2, next)) {
+               if (ec_config_cmp(v1, v2))
+                       return -1;
+       }
+       if (v1 != NULL || v2 != NULL)
+               return -1;
+
+       return 0;
+}
+
+/* XXX -> ec_keyval_cmp() */
+static int
+ec_config_dict_cmp(const struct ec_keyval *d1,
+               const struct ec_keyval *d2)
+{
+       const struct ec_config *v1, *v2;
+       struct ec_keyval_iter *iter = NULL;
+       const char *key;
+
+       if (ec_keyval_len(d1) != ec_keyval_len(d2))
+               return -1;
+
+       for (iter = ec_keyval_iter(d1);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               key = ec_keyval_iter_get_key(iter);
+               v1 = ec_keyval_iter_get_val(iter);
+               v2 = ec_keyval_get(d2, key);
+
+               if (ec_config_cmp(v1, v2))
+                       goto fail;
+       }
+
+       ec_keyval_iter_free(iter);
+       return 0;
+
+fail:
+       ec_keyval_iter_free(iter);
+       return -1;
+}
+
+int
+ec_config_cmp(const struct ec_config *value1,
+               const struct ec_config *value2)
+{
+       if (value1 == NULL || value2 == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (value1->type != value2->type)
+               return -1;
+
+       switch (value1->type) {
+       case EC_CONFIG_TYPE_BOOL:
+               if (value1->boolean == value2->boolean)
+                       return 0;
+       case EC_CONFIG_TYPE_INT64:
+               if (value1->i64 == value2->i64)
+                       return 0;
+       case EC_CONFIG_TYPE_UINT64:
+               if (value1->u64 == value2->u64)
+                       return 0;
+       case EC_CONFIG_TYPE_STRING:
+               if (!strcmp(value1->string, value2->string))
+                       return 0;
+       case EC_CONFIG_TYPE_NODE:
+               if (value1->node == value2->node)
+                       return 0;
+       case EC_CONFIG_TYPE_LIST:
+               return ec_config_list_cmp(&value1->list, &value2->list);
+       case EC_CONFIG_TYPE_DICT:
+               return ec_config_dict_cmp(value1->dict, value2->dict);
+       default:
+               break;
+       }
+
+       return -1;
+}
+
+static int
+ec_config_list_validate(const struct ec_config_list *list,
+                       const struct ec_config_schema *sch)
+{
+       const struct ec_config *value;
+
+       TAILQ_FOREACH(value, list, next) {
+               if (value->type != sch->type) {
+                       errno = EBADMSG;
+                       return -1;
+               }
+
+               if (value->type == EC_CONFIG_TYPE_LIST) {
+                       if (ec_config_list_validate(&value->list,
+                                                       sch->subschema) < 0)
+                               return -1;
+               } else if (value->type == EC_CONFIG_TYPE_DICT) {
+                       if (ec_config_dict_validate(value->dict,
+                                       sch->subschema) < 0)
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
+static int
+ec_config_dict_validate(const struct ec_keyval *dict,
+                       const struct ec_config_schema *schema)
+{
+       const struct ec_config *value;
+       struct ec_keyval_iter *iter = NULL;
+       const struct ec_config_schema *sch;
+       const char *key;
+
+       for (iter = ec_keyval_iter(dict);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+
+               key = ec_keyval_iter_get_key(iter);
+               value = ec_keyval_iter_get_val(iter);
+               sch = ec_config_schema_lookup(schema, key);
+               if (sch == NULL || sch->type != value->type) {
+                       errno = EBADMSG;
+                       goto fail;
+               }
+               if (value->type == EC_CONFIG_TYPE_LIST) {
+                       if (ec_config_list_validate(&value->list,
+                                                       sch->subschema) < 0)
+                               goto fail;
+               } else if (value->type == EC_CONFIG_TYPE_DICT) {
+                       if (ec_config_dict_validate(value->dict,
+                                       sch->subschema) < 0)
+                               goto fail;
+               }
+       }
+
+       ec_keyval_iter_free(iter);
+       return 0;
+
+fail:
+       ec_keyval_iter_free(iter);
+       return -1;
+}
+
+int
+ec_config_validate(const struct ec_config *dict,
+               const struct ec_config_schema *schema)
+{
+       if (dict->type != EC_CONFIG_TYPE_DICT || schema == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (ec_config_dict_validate(dict->dict, schema) < 0)
+               goto fail;
+
+       return 0
+;
+fail:
+       return -1;
+}
+
+struct ec_config *
+ec_config_dict_get(const struct ec_config *config, const char *key)
+{
+       if (config == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if (config->type != EC_CONFIG_TYPE_DICT) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       return ec_keyval_get(config->dict, key);
+}
+
+struct ec_config *
+ec_config_list_first(struct ec_config *list)
+{
+       if (list  == NULL || list->type != EC_CONFIG_TYPE_LIST) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       return TAILQ_FIRST(&list->list);
+}
+
+struct ec_config *
+ec_config_list_next(struct ec_config *list, struct ec_config *config)
+{
+       (void)list;
+       return TAILQ_NEXT(config, next);
+}
+
+/* value is consumed */
+int ec_config_dict_set(struct ec_config *config, const char *key,
+               struct ec_config *value)
+{
+       void (*free_cb)(struct ec_config *) = ec_config_free;
+
+       if (config == NULL || key == NULL || value == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+       if (config->type != EC_CONFIG_TYPE_DICT) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       return ec_keyval_set(config->dict, key, value,
+                       (void (*)(void *))free_cb);
+
+fail:
+       ec_config_free(value);
+       return -1;
+}
+
+int ec_config_dict_del(struct ec_config *config, const char *key)
+{
+       if (config == NULL || key == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+       if (config->type != EC_CONFIG_TYPE_DICT) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       return ec_keyval_del(config->dict, key);
+}
+
+/* value is consumed */
+int
+ec_config_list_add(struct ec_config *list,
+               struct ec_config *value)
+{
+       if (list == NULL || list->type != EC_CONFIG_TYPE_LIST || value == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       TAILQ_INSERT_TAIL(&list->list, value, next);
+
+       return 0;
+
+fail:
+       ec_config_free(value);
+       return -1;
+}
+
+int ec_config_list_del(struct ec_config *list, struct ec_config *config)
+{
+       if (list == NULL || list->type != EC_CONFIG_TYPE_LIST) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       TAILQ_REMOVE(&list->list, config, next);
+       ec_config_free(config);
+       return 0;
+}
+
+static struct ec_config *
+ec_config_list_dup(const struct ec_config_list *list)
+{
+       struct ec_config *dup = NULL, *v, *value;
+
+       dup = ec_config_list();
+       if (dup == NULL)
+               goto fail;
+
+       TAILQ_FOREACH(v, list, next) {
+               value = ec_config_dup(v);
+               if (value == NULL)
+                       goto fail;
+               if (ec_config_list_add(dup, value) < 0)
+                       goto fail;
+       }
+
+       return dup;
+
+fail:
+       ec_config_free(dup);
+       return NULL;
+}
+
+static struct ec_config *
+ec_config_dict_dup(const struct ec_keyval *dict)
+{
+       struct ec_config *dup = NULL, *value;
+       struct ec_keyval_iter *iter = NULL;
+       const char *key;
+
+       dup = ec_config_dict();
+       if (dup == NULL)
+               goto fail;
+
+       for (iter = ec_keyval_iter(dict);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               key = ec_keyval_iter_get_key(iter);
+               value = ec_config_dup(ec_keyval_iter_get_val(iter));
+               if (value == NULL)
+                       goto fail;
+               if (ec_config_dict_set(dup, key, value) < 0)
+                       goto fail;
+       }
+       ec_keyval_iter_free(iter);
+
+       return dup;
+
+fail:
+       ec_config_free(dup);
+       ec_keyval_iter_free(iter);
+       return NULL;
+}
+
+struct ec_config *
+ec_config_dup(const struct ec_config *config)
+{
+       if (config == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       switch (config->type) {
+       case EC_CONFIG_TYPE_BOOL:
+               return ec_config_bool(config->boolean);
+       case EC_CONFIG_TYPE_INT64:
+               return ec_config_i64(config->i64);
+       case EC_CONFIG_TYPE_UINT64:
+               return ec_config_u64(config->u64);
+       case EC_CONFIG_TYPE_STRING:
+               return ec_config_string(config->string);
+       case EC_CONFIG_TYPE_NODE:
+               return ec_config_node(ec_node_clone(config->node));
+       case EC_CONFIG_TYPE_LIST:
+               return ec_config_list_dup(&config->list);
+       case EC_CONFIG_TYPE_DICT:
+               return ec_config_dict_dup(config->dict);
+       default:
+               errno = EINVAL;
+               break;
+       }
+
+       return NULL;
+}
+
+static int
+ec_config_list_dump(FILE *out, const char *key,
+               const struct ec_config_list *list, size_t indent)
+{
+       const struct ec_config *v;
+
+       fprintf(out, "%*s" "%s%s%stype=list\n", (int)indent * 4, "",
+               key ? "key=": "",
+               key ? key: "",
+               key ? " ": "");
+
+       TAILQ_FOREACH(v, list, next) {
+               if (__ec_config_dump(out, NULL, v, indent + 1) < 0)
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int
+ec_config_dict_dump(FILE *out, const char *key, const struct ec_keyval *dict,
+               size_t indent)
+{
+       const struct ec_config *value;
+       struct ec_keyval_iter *iter;
+       const char *k;
+
+       fprintf(out, "%*s" "%s%s%stype=dict\n", (int)indent * 4, "",
+               key ? "key=": "",
+               key ? key: "",
+               key ? " ": "");
+
+       for (iter = ec_keyval_iter(dict);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               k = ec_keyval_iter_get_key(iter);
+               value = ec_keyval_iter_get_val(iter);
+               if (__ec_config_dump(out, k, value, indent + 1) < 0)
+                       goto fail;
+       }
+       ec_keyval_iter_free(iter);
+       return 0;
+
+fail:
+       ec_keyval_iter_free(iter);
+       return -1;
+}
+
+static int
+__ec_config_dump(FILE *out, const char *key, const struct ec_config *value,
+               size_t indent)
+{
+       char *val_str = NULL;
+
+       switch (value->type) {
+       case EC_CONFIG_TYPE_BOOL:
+               if (value->boolean)
+                       ec_asprintf(&val_str, "true");
+               else
+                       ec_asprintf(&val_str, "false");
+               break;
+       case EC_CONFIG_TYPE_INT64:
+               ec_asprintf(&val_str, "%"PRIu64, value->u64);
+               break;
+       case EC_CONFIG_TYPE_UINT64:
+               ec_asprintf(&val_str, "%"PRIi64, value->i64);
+               break;
+       case EC_CONFIG_TYPE_STRING:
+               ec_asprintf(&val_str, "%s", value->string);
+               break;
+       case EC_CONFIG_TYPE_NODE:
+               ec_asprintf(&val_str, "%p", value->node);
+               break;
+       case EC_CONFIG_TYPE_LIST:
+               return ec_config_list_dump(out, key, &value->list, indent);
+       case EC_CONFIG_TYPE_DICT:
+               return ec_config_dict_dump(out, key, value->dict, indent);
+       default:
+               errno = EINVAL;
+               break;
+       }
+
+       /* errno is already set on error */
+       if (val_str == NULL)
+               goto fail;
+
+       fprintf(out, "%*s" "%s%s%stype=%s val=%s\n", (int)indent * 4, "",
+               key ? "key=": "",
+               key ? key: "",
+               key ? " ": "",
+               ec_config_type_str(value->type), val_str);
+
+       ec_free(val_str);
+       return 0;
+
+fail:
+       ec_free(val_str);
+       return -1;
+}
+
+void
+ec_config_dump(FILE *out, const struct ec_config *config)
+{
+       fprintf(out, "------------------- config dump:\n");
+
+       if (config == NULL) {
+               fprintf(out, "no config\n");
+               return;
+       }
+
+       if (__ec_config_dump(out, NULL, config, 0) < 0)
+               fprintf(out, "error while dumping\n");
+}
+
+/* LCOV_EXCL_START */
+static const struct ec_config_schema sch_intlist_elt[] = {
+       {
+               .desc = "This is a description for int",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema sch_dict[] = {
+       {
+               .key = "my_int",
+               .desc = "This is a description for int",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .key = "my_int2",
+               .desc = "This is a description for int2",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema sch_dictlist_elt[] = {
+       {
+               .desc = "This is a description for dict",
+               .type = EC_CONFIG_TYPE_DICT,
+               .subschema = sch_dict,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema sch_baseconfig[] = {
+       {
+               .key = "my_bool",
+               .desc = "This is a description for bool",
+               .type = EC_CONFIG_TYPE_BOOL,
+       },
+       {
+               .key = "my_int",
+               .desc = "This is a description for int",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .key = "my_string",
+               .desc = "This is a description for string",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .key = "my_node",
+               .desc = "This is a description for node",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .key = "my_intlist",
+               .desc = "This is a description for list",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = sch_intlist_elt,
+       },
+       {
+               .key = "my_dictlist",
+               .desc = "This is a description for list",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = sch_dictlist_elt,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_config_testcase(void)
+{
+       struct ec_node *node = NULL;
+       struct ec_keyval *dict = NULL;
+       const struct ec_config *value = NULL;
+       struct ec_config *config = NULL, *config2 = NULL;
+       struct ec_config *list = NULL, *subconfig = NULL;
+       struct ec_config *list_, *config_;
+       int testres = 0;
+       int ret;
+
+       testres |= EC_TEST_CHECK(ec_config_key_is_reserved("id"),
+               "'id' should be reserved");
+       testres |= EC_TEST_CHECK(!ec_config_key_is_reserved("foo"),
+               "'foo' should not be reserved");
+
+       node = ec_node("empty", EC_NO_ID);
+       if (node == NULL)
+               goto fail;
+
+       if (ec_config_schema_validate(sch_baseconfig) < 0) {
+               EC_LOG(EC_LOG_ERR, "invalid config schema\n");
+               goto fail;
+       }
+
+       ec_config_schema_dump(stdout, sch_baseconfig);
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "my_bool", ec_config_bool(true));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set boolean");
+       value = ec_config_dict_get(config, "my_bool");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_BOOL &&
+               value->boolean == true,
+               "unexpected boolean value");
+
+       ret = ec_config_dict_set(config, "my_int", ec_config_i64(1234));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(config, "my_int");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 1234,
+               "unexpected int value");
+
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(config, sch_baseconfig) == 0,
+               "cannot validate config\n");
+
+       ret = ec_config_dict_set(config, "my_string", ec_config_string("toto"));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set string");
+       value = ec_config_dict_get(config, "my_string");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_STRING &&
+               !strcmp(value->string, "toto"),
+               "unexpected string value");
+
+       list = ec_config_list();
+       if (list == NULL)
+               goto fail;
+
+       subconfig = ec_config_dict();
+       if (subconfig == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(1));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(subconfig, "my_int");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 1,
+               "unexpected int value");
+
+       ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(2));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(subconfig, "my_int2");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 2,
+               "unexpected int value");
+
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(subconfig, sch_dict) == 0,
+               "cannot validate subconfig\n");
+
+       ret = ec_config_list_add(list, subconfig);
+       subconfig = NULL; /* freed */
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add in list");
+
+       subconfig = ec_config_dict();
+       if (subconfig == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(3));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(subconfig, "my_int");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 3,
+               "unexpected int value");
+
+       ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(4));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(subconfig, "my_int2");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 4,
+               "unexpected int value");
+
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(subconfig, sch_dict) == 0,
+               "cannot validate subconfig\n");
+
+       ret = ec_config_list_add(list, subconfig);
+       subconfig = NULL; /* freed */
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add in list");
+
+       ret = ec_config_dict_set(config, "my_dictlist", list);
+       list = NULL;
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set list");
+
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(config, sch_baseconfig) == 0,
+               "cannot validate config\n");
+
+       list_ = ec_config_dict_get(config, "my_dictlist");
+       for (config_ = ec_config_list_first(list_); config_ != NULL;
+            config_ = ec_config_list_next(list_, config_)) {
+               ec_config_dump(stdout, config_);
+       }
+
+       ec_config_dump(stdout, config);
+
+       config2 = ec_config_dup(config);
+       testres |= EC_TEST_CHECK(config2 != NULL, "cannot duplicate config");
+       testres |= EC_TEST_CHECK(
+               ec_config_cmp(config, config2) == 0,
+               "fail to compare config");
+       ec_config_free(config2);
+       config2 = NULL;
+
+       /* remove the first element */
+       ec_config_list_del(list_, ec_config_list_first(list_));
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(config, sch_baseconfig) == 0,
+               "cannot validate config\n");
+
+       ec_config_dump(stdout, config);
+
+       ec_config_free(list);
+       ec_config_free(subconfig);
+       ec_config_free(config);
+       ec_keyval_free(dict);
+       ec_node_free(node);
+
+       return testres;
+
+fail:
+       ec_config_free(list);
+       ec_config_free(subconfig);
+       ec_config_free(config);
+       ec_config_free(config2);
+       ec_keyval_free(dict);
+       ec_node_free(node);
+
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_config_test = {
+       .name = "config",
+       .test = ec_config_testcase,
+};
+
+EC_TEST_REGISTER(ec_config_test);
diff --git a/libecoli/ecoli_config.h b/libecoli/ecoli_config.h
new file mode 100644 (file)
index 0000000..9d7c628
--- /dev/null
@@ -0,0 +1,392 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_CONFIG_
+#define ECOLI_CONFIG_
+
+#include <sys/queue.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#ifndef EC_COUNT_OF //XXX
+#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
+               ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+#endif
+
+struct ec_config;
+struct ec_keyval;
+
+/**
+ * The type identifier for a config value.
+ */
+enum ec_config_type {
+       EC_CONFIG_TYPE_NONE = 0,
+       EC_CONFIG_TYPE_BOOL,
+       EC_CONFIG_TYPE_INT64,
+       EC_CONFIG_TYPE_UINT64,
+       EC_CONFIG_TYPE_STRING,
+       EC_CONFIG_TYPE_NODE,
+       EC_CONFIG_TYPE_LIST,
+       EC_CONFIG_TYPE_DICT,
+};
+
+/**
+ * Structure describing the format of a configuration value.
+ *
+ * This structure is used in a const array which is referenced by a
+ * struct ec_config. Each entry of the array represents a key/value
+ * storage of the configuration dictionary.
+ */
+struct ec_config_schema {
+       const char *key;          /**< The key string (NULL for list elts). */
+       const char *desc;         /**< A description of the value. */
+       enum ec_config_type type; /**< Type of the value */
+
+       /** If type is dict or list, the schema of the dict or list
+        * elements. Else must be NULL. */
+       const struct ec_config_schema *subschema;
+};
+
+TAILQ_HEAD(ec_config_list, ec_config);
+
+/**
+ * Structure storing the configuration data.
+ */
+struct ec_config {
+       /** type of value stored in the union */
+       enum ec_config_type type;
+
+       union {
+               bool boolean;   /** Boolean value */
+               int64_t i64;    /** Signed integer value */
+               uint64_t u64;   /** Unsigned integer value */
+               char *string;   /** String value */
+               struct ec_node *node;       /** Node value */
+               struct ec_keyval *dict;     /** Hash table value */
+               struct ec_config_list list; /** List value */
+       };
+
+       /**
+        * Next in list, only valid if type is list.
+        */
+       TAILQ_ENTRY(ec_config) next;
+};
+
+/* schema */
+
+/**
+ * Validate a configuration schema array.
+ *
+ * @param schema
+ *   Pointer to the first element of the schema array. The array
+ *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
+ * @return
+ *   0 if the schema is valid, or -1 on error (errno is set).
+ */
+int ec_config_schema_validate(const struct ec_config_schema *schema);
+
+/**
+ * Dump a configuration schema array.
+ *
+ * @param out
+ *   Output stream on which the dump will be sent.
+ * @param schema
+ *   Pointer to the first element of the schema array. The array
+ *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
+ */
+void ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema);
+
+/**
+ * Find a schema entry matching the key.
+ *
+ * Browse the schema array and lookup for the given key.
+ *
+ * @param schema
+ *   Pointer to the first element of the schema array. The array
+ *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
+ * @return
+ *   The schema entry if it matches a key, or NULL if not found.
+ */
+const struct ec_config_schema *
+ec_config_schema_lookup(const struct ec_config_schema *schema,
+                       const char *key);
+
+/**
+ * Get the type of a schema entry.
+ *
+ * @param schema_elt
+ *   Pointer to an element of the schema array.
+ * @return
+ *   The type of the schema entry.
+ */
+enum ec_config_type
+ec_config_schema_type(const struct ec_config_schema *schema_elt);
+
+/**
+ * Get the subschema of a schema entry.
+ *
+ * @param schema_elt
+ *   Pointer to an element of the schema array.
+ * @return
+ *   The subschema if any, or NULL.
+ */
+const struct ec_config_schema *
+ec_config_schema_sub(const struct ec_config_schema *schema_elt);
+
+/**
+ * Check if a key name is reserved in a config dict.
+ *
+ * Some key names are reserved and should not be used in configs.
+ *
+ * @param name
+ *   The name of the key to test.
+ * @return
+ *   True if the key name is reserved and must not be used, else false.
+ */
+bool ec_config_key_is_reserved(const char *name);
+
+/**
+ * Array of reserved key names.
+ */
+extern const char *ec_config_reserved_keys[];
+
+
+/* config */
+
+/**
+ * Get the type of the configuration.
+ *
+ * @param config
+ *   The configuration.
+ * @return
+ *   The configuration type.
+ */
+enum ec_config_type ec_config_get_type(const struct ec_config *config);
+
+/**
+ * Create a boolean configuration value.
+ *
+ * @param boolean
+ *   The boolean value to be set.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_bool(bool boolean);
+
+/**
+ * Create a signed integer configuration value.
+ *
+ * @param i64
+ *   The signed integer value to be set.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_i64(int64_t i64);
+
+/**
+ * Create an unsigned configuration value.
+ *
+ * @param u64
+ *   The unsigned integer value to be set.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_u64(uint64_t u64);
+
+/**
+ * Create a string configuration value.
+ *
+ * @param string
+ *   The string value to be set. The string is copied into the
+ *   configuration object.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_string(const char *string);
+
+/**
+ * Create a node configuration value.
+ *
+ * @param node
+ *   The node pointer to be set. The node is "consumed" by
+ *   the function and should not be used by the caller, even
+ *   on error. The caller can use ec_node_clone() to keep a
+ *   reference on the node.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_node(struct ec_node *node);
+
+/**
+ * Create a hash table configuration value.
+ *
+ * @return
+ *   A configuration object containing an empty hash table, or NULL on
+ *   error (errno is set).
+ */
+struct ec_config *ec_config_dict(void);
+
+/**
+ * Create a list configuration value.
+ *
+ * @return
+ *   The configuration object containing an empty list, or NULL on
+ *   error (errno is set).
+ */
+struct ec_config *ec_config_list(void);
+
+/**
+ * Add a config object into a list.
+ *
+ * @param list
+ *   The list configuration in which the value will be added.
+ * @param value
+ *   The value configuration to add in the list. The value object
+ *   will be freed when freeing the list object. On error, the
+ *   value object is also freed.
+ * @return
+ *   0 on success, else -1 (errno is set).
+ */
+int ec_config_list_add(struct ec_config *list, struct ec_config *value);
+
+/**
+ * Remove an element from a list.
+ *
+ * The element is freed and should not be accessed.
+ *
+ * @param list
+ *   The list configuration.
+ * @param config
+ *   The element to remove from the list.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_config_list_del(struct ec_config *list, struct ec_config *config);
+
+/**
+ * Validate a configuration.
+ *
+ * @param dict
+ *   A hash table configuration to validate.
+ * @param schema
+ *   Pointer to the first element of the schema array. The array
+ *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_config_validate(const struct ec_config *dict,
+               const struct ec_config_schema *schema);
+
+/**
+ * Set a value in a hash table configuration
+ *
+ * @param dict
+ *   A hash table configuration to validate.
+ * @param key
+ *   The key to update.
+ * @param value
+ *   The value to set. The value object will be freed when freeing the
+ *   dict object. On error, the value object is also freed.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_config_dict_set(struct ec_config *dict, const char *key,
+               struct ec_config *value);
+
+/**
+ * Remove an element from a hash table configuration.
+ *
+ * The element is freed and should not be accessed.
+ *
+ * @param dict
+ *   A hash table configuration to validate.
+ * @param key
+ *   The key of the configuration to delete.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_config_dict_del(struct ec_config *config, const char *key);
+
+/**
+ * Compare two configurations.
+ */
+int ec_config_cmp(const struct ec_config *config1,
+               const struct ec_config *config2);
+
+/**
+ * Get configuration value.
+ */
+struct ec_config *ec_config_dict_get(const struct ec_config *config,
+                               const char *key);
+
+/**
+ * Get the first element of a list.
+ *
+ * Example of use:
+ * for (config = ec_config_list_iter(list);
+ *     config != NULL;
+ *     config = ec_config_list_next(list, config)) {
+ *             ...
+ * }
+ *
+ * @param list
+ *   The list configuration to iterate.
+ * @return
+ *   The first configuration element, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_list_first(struct ec_config *list);
+
+/**
+ * Get next element in list.
+ *
+ * @param list
+ *   The list configuration beeing iterated.
+ * @param config
+ *   The current configuration element.
+ * @return
+ *   The next configuration element, or NULL if there is no more element.
+ */
+struct ec_config *
+ec_config_list_next(struct ec_config *list, struct ec_config *config);
+
+/**
+ * Free a configuration.
+ *
+ * @param config
+ *   The element to free.
+ */
+void ec_config_free(struct ec_config *config);
+
+/**
+ * Compare two configurations.
+ *
+ * @return
+ *   0 if the configurations are equal, else -1.
+ */
+int ec_config_cmp(const struct ec_config *value1,
+               const struct ec_config *value2);
+
+/**
+ * Duplicate a configuration.
+ *
+ * @param config
+ *   The configuration to duplicate.
+ * @return
+ *   The duplicated configuration, or NULL on error (errno is set).
+ */
+struct ec_config *
+ec_config_dup(const struct ec_config *config);
+
+/**
+ * Dump a configuration.
+ *
+ * @param out
+ *   Output stream on which the dump will be sent.
+ * @param config
+ *   The configuration to dump.
+ */
+void ec_config_dump(FILE *out, const struct ec_config *config);
+
+#endif
diff --git a/libecoli/ecoli_init.c b/libecoli/ecoli_init.c
new file mode 100644 (file)
index 0000000..fd5c0c3
--- /dev/null
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <ecoli_init.h>
+
+static struct ec_init_list init_list = TAILQ_HEAD_INITIALIZER(init_list);
+
+/* register an init function */
+void ec_init_register(struct ec_init *init)
+{
+       struct ec_init *cur;
+
+       if (TAILQ_EMPTY(&init_list)) {
+               TAILQ_INSERT_HEAD(&init_list, init, next);
+               return;
+       }
+
+
+       TAILQ_FOREACH(cur, &init_list, next) {
+               if (init->priority > cur->priority)
+                       continue;
+
+               TAILQ_INSERT_BEFORE(cur, init, next);
+               return;
+       }
+
+       TAILQ_INSERT_TAIL(&init_list, init, next);
+}
+
+int ec_init(void)
+{
+       struct ec_init *init;
+
+       TAILQ_FOREACH(init, &init_list, next) {
+               if (init->init() < 0)
+                       return -1;
+       }
+
+       return 0;
+}
diff --git a/libecoli/ecoli_init.h b/libecoli/ecoli_init.h
new file mode 100644 (file)
index 0000000..4e8bc1e
--- /dev/null
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Register initialization routines.
+ */
+
+#ifndef ECOLI_INIT_
+#define ECOLI_INIT_
+
+#include <sys/queue.h>
+
+#include <ecoli_log.h>
+#include <ecoli_node.h>
+
+#define EC_INIT_REGISTER(t)                                            \
+       static void ec_init_init_##t(void);                             \
+       static void __attribute__((constructor, used))                  \
+       ec_init_init_##t(void)                                          \
+       {                                                               \
+                ec_init_register(&t);                                  \
+       }
+
+/**
+ * Type of init function. Return 0 on success, -1 on error.
+ */
+typedef int (ec_init_t)(void);
+
+TAILQ_HEAD(ec_init_list, ec_init);
+
+/**
+ * A structure describing a test case.
+ */
+struct ec_init {
+       TAILQ_ENTRY(ec_init) next;  /**< Next in list. */
+       ec_init_t *init;            /**< Init function. */
+       unsigned int priority;      /**< Priority (0=first, 99=last) */
+};
+
+/**
+ * Register an initialization function.
+ *
+ * @param init
+ *   A pointer to a ec_init structure to be registered.
+ */
+void ec_init_register(struct ec_init *test);
+
+/**
+ * Initialize ecoli library
+ *
+ * Must be called before any other function from libecoli, except
+ * ec_malloc_register().
+ *
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_init(void);
+
+#endif
diff --git a/libecoli/ecoli_keyval.c b/libecoli/ecoli_keyval.c
new file mode 100644 (file)
index 0000000..12fe66b
--- /dev/null
@@ -0,0 +1,569 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <ecoli_init.h>
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_murmurhash.h>
+#include <ecoli_keyval.h>
+
+#define FACTOR 3
+
+EC_LOG_TYPE_REGISTER(keyval);
+
+static uint32_t ec_keyval_seed;
+
+struct ec_keyval_elt {
+       char *key;
+       void *val;
+       uint32_t hash;
+       ec_keyval_elt_free_t free;
+       unsigned int refcount;
+};
+
+struct ec_keyval_elt_ref {
+       LIST_ENTRY(ec_keyval_elt_ref) next;
+       struct ec_keyval_elt *elt;
+};
+
+LIST_HEAD(ec_keyval_elt_ref_list, ec_keyval_elt_ref);
+
+struct ec_keyval {
+       size_t len;
+       size_t table_size;
+       struct ec_keyval_elt_ref_list *table;
+};
+
+struct ec_keyval_iter {
+       const struct ec_keyval *keyval;
+       size_t cur_idx;
+       const struct ec_keyval_elt_ref *cur_ref;
+};
+
+struct ec_keyval *ec_keyval(void)
+{
+       struct ec_keyval *keyval;
+
+       keyval = ec_calloc(1, sizeof(*keyval));
+       if (keyval == NULL)
+               return NULL;
+
+       return keyval;
+}
+
+static struct ec_keyval_elt_ref *
+ec_keyval_lookup(const struct ec_keyval *keyval, const char *key)
+{
+       struct ec_keyval_elt_ref *ref;
+       uint32_t h, mask = keyval->table_size - 1;
+
+       if (keyval == NULL || key == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+       if (keyval->table_size == 0) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       h = ec_murmurhash3(key, strlen(key), ec_keyval_seed);
+       LIST_FOREACH(ref, &keyval->table[h & mask], next) {
+               if (strcmp(ref->elt->key, key) == 0)
+                       return ref;
+       }
+
+       errno = ENOENT;
+       return NULL;
+}
+
+static void ec_keyval_elt_ref_free(struct ec_keyval_elt_ref *ref)
+{
+       struct ec_keyval_elt *elt;
+
+       if (ref == NULL)
+               return;
+
+       elt = ref->elt;
+       if (elt != NULL && --elt->refcount == 0) {
+               ec_free(elt->key);
+               if (elt->free != NULL)
+                       elt->free(elt->val);
+               ec_free(elt);
+       }
+       ec_free(ref);
+}
+
+bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key)
+{
+       return !!ec_keyval_lookup(keyval, key);
+}
+
+void *ec_keyval_get(const struct ec_keyval *keyval, const char *key)
+{
+       struct ec_keyval_elt_ref *ref;
+
+       ref = ec_keyval_lookup(keyval, key);
+       if (ref == NULL)
+               return NULL;
+
+       return ref->elt->val;
+}
+
+int ec_keyval_del(struct ec_keyval *keyval, const char *key)
+{
+       struct ec_keyval_elt_ref *ref;
+
+       ref = ec_keyval_lookup(keyval, key);
+       if (ref == NULL)
+               return -1;
+
+       /* we could resize table here */
+
+       LIST_REMOVE(ref, next);
+       ec_keyval_elt_ref_free(ref);
+       keyval->len--;
+
+       return 0;
+}
+
+static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size)
+{
+       struct ec_keyval_elt_ref_list *new_table;
+       struct ec_keyval_elt_ref *ref;
+       size_t i;
+
+       if (new_size == 0 || (new_size & (new_size - 1))) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       new_table = ec_calloc(new_size, sizeof(*keyval->table));
+       if (new_table == NULL)
+               return -1;
+
+       for (i = 0; i < keyval->table_size; i++) {
+               while (!LIST_EMPTY(&keyval->table[i])) {
+                       ref = LIST_FIRST(&keyval->table[i]);
+                       LIST_REMOVE(ref, next);
+                       LIST_INSERT_HEAD(
+                               &new_table[ref->elt->hash & (new_size - 1)],
+                               ref, next);
+               }
+       }
+
+       ec_free(keyval->table);
+       keyval->table = new_table;
+       keyval->table_size = new_size;
+
+       return 0;
+}
+
+static int
+__ec_keyval_set(struct ec_keyval *keyval, struct ec_keyval_elt_ref *ref)
+{
+       size_t new_size;
+       uint32_t mask;
+       int ret;
+
+       /* remove previous entry if any */
+       ec_keyval_del(keyval, ref->elt->key);
+
+       if (keyval->len >= keyval->table_size) {
+               if (keyval->table_size != 0)
+                       new_size =  keyval->table_size << FACTOR;
+               else
+                       new_size =  1 << FACTOR;
+               ret = ec_keyval_table_resize(keyval, new_size);
+               if (ret < 0)
+                       return ret;
+       }
+
+       mask = keyval->table_size - 1;
+       LIST_INSERT_HEAD(&keyval->table[ref->elt->hash & mask], ref, next);
+       keyval->len++;
+
+       return 0;
+}
+
+int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val,
+       ec_keyval_elt_free_t free_cb)
+{
+       struct ec_keyval_elt *elt = NULL;
+       struct ec_keyval_elt_ref *ref = NULL;
+       uint32_t h;
+
+       if (keyval == NULL || key == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       ref = ec_calloc(1, sizeof(*ref));
+       if (ref == NULL)
+               goto fail;
+
+       elt = ec_calloc(1, sizeof(*elt));
+       if (elt == NULL)
+               goto fail;
+
+       ref->elt = elt;
+       elt->refcount = 1;
+       elt->val = val;
+       val = NULL;
+       elt->free = free_cb;
+       elt->key = ec_strdup(key);
+       if (elt->key == NULL)
+               goto fail;
+       h = ec_murmurhash3(key, strlen(key), ec_keyval_seed);
+       elt->hash = h;
+
+       if (__ec_keyval_set(keyval, ref) < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       if (free_cb != NULL && val != NULL)
+               free_cb(val);
+       ec_keyval_elt_ref_free(ref);
+       return -1;
+}
+
+void ec_keyval_free(struct ec_keyval *keyval)
+{
+       struct ec_keyval_elt_ref *ref;
+       size_t i;
+
+       if (keyval == NULL)
+               return;
+
+       for (i = 0; i < keyval->table_size; i++) {
+               while (!LIST_EMPTY(&keyval->table[i])) {
+                       ref = LIST_FIRST(&keyval->table[i]);
+                       LIST_REMOVE(ref, next);
+                       ec_keyval_elt_ref_free(ref);
+               }
+       }
+       ec_free(keyval->table);
+       ec_free(keyval);
+}
+
+size_t ec_keyval_len(const struct ec_keyval *keyval)
+{
+       return keyval->len;
+}
+
+void
+ec_keyval_iter_free(struct ec_keyval_iter *iter)
+{
+       ec_free(iter);
+}
+
+struct ec_keyval_iter *
+ec_keyval_iter(const struct ec_keyval *keyval)
+{
+       struct ec_keyval_iter *iter = NULL;
+
+       iter = ec_calloc(1, sizeof(*iter));
+       if (iter == NULL)
+               return NULL;
+
+       iter->keyval = keyval;
+       iter->cur_idx = 0;
+       iter->cur_ref = NULL;
+
+       ec_keyval_iter_next(iter);
+
+       return iter;
+}
+
+void
+ec_keyval_iter_next(struct ec_keyval_iter *iter)
+{
+       const struct ec_keyval_elt_ref *ref;
+       size_t i;
+
+       i = iter->cur_idx;
+       if (i == iter->keyval->table_size)
+               return; /* no more element */
+
+       if (iter->cur_ref != NULL) {
+               ref = LIST_NEXT(iter->cur_ref, next);
+               if (ref != NULL) {
+                       iter->cur_ref = ref;
+                       return;
+               }
+               i++;
+       }
+
+       for (; i < iter->keyval->table_size; i++) {
+               LIST_FOREACH(ref, &iter->keyval->table[i], next) {
+                       iter->cur_idx = i;
+                       iter->cur_ref = LIST_FIRST(&iter->keyval->table[i]);
+                       return;
+               }
+       }
+
+       iter->cur_idx = iter->keyval->table_size;
+       iter->cur_ref = NULL;
+}
+
+bool
+ec_keyval_iter_valid(const struct ec_keyval_iter *iter)
+{
+       if (iter == NULL || iter->cur_ref == NULL)
+               return false;
+
+       return true;
+}
+
+const char *
+ec_keyval_iter_get_key(const struct ec_keyval_iter *iter)
+{
+       if (iter == NULL || iter->cur_ref == NULL)
+               return NULL;
+
+       return iter->cur_ref->elt->key;
+}
+
+void *
+ec_keyval_iter_get_val(const struct ec_keyval_iter *iter)
+{
+       if (iter == NULL || iter->cur_ref == NULL)
+               return NULL;
+
+       return iter->cur_ref->elt->val;
+}
+
+void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval)
+{
+       struct ec_keyval_iter *iter;
+
+       if (keyval == NULL) {
+               fprintf(out, "empty keyval\n");
+               return;
+       }
+
+       fprintf(out, "keyval:\n");
+       for (iter = ec_keyval_iter(keyval);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               fprintf(out, "  %s: %p\n",
+                       ec_keyval_iter_get_key(iter),
+                       ec_keyval_iter_get_val(iter));
+       }
+       ec_keyval_iter_free(iter);
+}
+
+struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval)
+{
+       struct ec_keyval *dup = NULL;
+       struct ec_keyval_elt_ref *ref, *dup_ref = NULL;
+       size_t i;
+
+       dup = ec_keyval();
+       if (dup == NULL)
+               return NULL;
+
+       for (i = 0; i < keyval->table_size; i++) {
+               LIST_FOREACH(ref, &keyval->table[i], next) {
+                       dup_ref = ec_calloc(1, sizeof(*ref));
+                       if (dup_ref == NULL)
+                               goto fail;
+                       dup_ref->elt = ref->elt;
+                       ref->elt->refcount++;
+
+                       if (__ec_keyval_set(dup, dup_ref) < 0)
+                               goto fail;
+               }
+       }
+
+       return dup;
+
+fail:
+       ec_keyval_elt_ref_free(dup_ref);
+       ec_keyval_free(dup);
+       return NULL;
+}
+
+static int ec_keyval_init_func(void)
+{
+       int fd;
+       ssize_t ret;
+
+       return 0;//XXX for test reproduceability
+
+       fd = open("/dev/urandom", 0);
+       if (fd == -1) {
+               fprintf(stderr, "failed to open /dev/urandom\n");
+               return -1;
+       }
+       ret = read(fd, &ec_keyval_seed, sizeof(ec_keyval_seed));
+       if (ret != sizeof(ec_keyval_seed)) {
+               fprintf(stderr, "failed to read /dev/urandom\n");
+               return -1;
+       }
+       close(fd);
+       return 0;
+}
+
+static struct ec_init ec_keyval_init = {
+       .init = ec_keyval_init_func,
+       .priority = 50,
+};
+
+EC_INIT_REGISTER(ec_keyval_init);
+
+/* LCOV_EXCL_START */
+static int ec_keyval_testcase(void)
+{
+       struct ec_keyval *keyval, *dup;
+       struct ec_keyval_iter *iter;
+       char *val;
+       size_t i, count;
+       int ret, testres = 0;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+
+       keyval = ec_keyval();
+       if (keyval == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create keyval\n");
+               return -1;
+       }
+
+       count = 0;
+       for (iter = ec_keyval_iter(keyval);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               count++;
+       }
+       ec_keyval_iter_free(iter);
+       testres |= EC_TEST_CHECK(count == 0, "invalid count in iterator");
+
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0, "bad keyval len");
+       ret = ec_keyval_set(keyval, "key1", "val1", NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       ret = ec_keyval_set(keyval, "key2", ec_strdup("val2"), ec_free_func);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2, "bad keyval len");
+
+       val = ec_keyval_get(keyval, "key1");
+       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val1"),
+                               "invalid keyval value");
+       val = ec_keyval_get(keyval, "key2");
+       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val2"),
+                               "invalid keyval value");
+       val = ec_keyval_get(keyval, "key3");
+       testres |= EC_TEST_CHECK(val == NULL, "key3 should be NULL");
+
+       ret = ec_keyval_set(keyval, "key1", "another_val1", NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       ret = ec_keyval_set(keyval, "key2", ec_strdup("another_val2"),
+                       ec_free_func);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2,
+                               "bad keyval len");
+
+       val = ec_keyval_get(keyval, "key1");
+       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val1"),
+               "invalid keyval value");
+       val = ec_keyval_get(keyval, "key2");
+       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val2"),
+               "invalid keyval value");
+       testres |= EC_TEST_CHECK(ec_keyval_has_key(keyval, "key1"),
+               "key1 should be in keyval");
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_keyval_dump(f, NULL);
+       fclose(f);
+       f = NULL;
+       free(buf);
+       buf = NULL;
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_keyval_dump(f, keyval);
+       fclose(f);
+       f = NULL;
+       free(buf);
+       buf = NULL;
+
+       ret = ec_keyval_del(keyval, "key1");
+       testres |= EC_TEST_CHECK(ret == 0, "cannot del key1");
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 1,
+               "invalid keyval len");
+       ret = ec_keyval_del(keyval, "key2");
+       testres |= EC_TEST_CHECK(ret == 0, "cannot del key2");
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0,
+               "invalid keyval len");
+
+       for (i = 0; i < 100; i++) {
+               char key[8];
+               snprintf(key, sizeof(key), "k%zd", i);
+               ret = ec_keyval_set(keyval, key, "val", NULL);
+               testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       }
+       dup = ec_keyval_dup(keyval);
+       testres |= EC_TEST_CHECK(dup != NULL, "cannot duplicate keyval");
+       if (dup != NULL) {
+               for (i = 0; i < 100; i++) {
+                       char key[8];
+                       snprintf(key, sizeof(key), "k%zd", i);
+                       val = ec_keyval_get(dup, key);
+                       testres |= EC_TEST_CHECK(
+                               val != NULL && !strcmp(val, "val"),
+                               "invalid keyval value");
+               }
+               ec_keyval_free(dup);
+               dup = NULL;
+       }
+
+       count = 0;
+       for (iter = ec_keyval_iter(keyval);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               count++;
+       }
+       ec_keyval_iter_free(iter);
+       testres |= EC_TEST_CHECK(count == 100, "invalid count in iterator");
+
+       /* einval */
+       ret = ec_keyval_set(keyval, NULL, "val1", NULL);
+       testres |= EC_TEST_CHECK(ret == -1, "should not be able to set key");
+       val = ec_keyval_get(keyval, NULL);
+       testres |= EC_TEST_CHECK(val == NULL, "get(NULL) should no success");
+
+       ec_keyval_free(keyval);
+
+       return testres;
+
+fail:
+       ec_keyval_free(keyval);
+       if (f)
+               fclose(f);
+       free(buf);
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_keyval_test = {
+       .name = "keyval",
+       .test = ec_keyval_testcase,
+};
+
+EC_TEST_REGISTER(ec_keyval_test);
diff --git a/libecoli/ecoli_keyval.h b/libecoli/ecoli_keyval.h
new file mode 100644 (file)
index 0000000..a3e9400
--- /dev/null
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Simple hash table API
+ *
+ * This file provides functions to store objects in hash tables, using strings
+ * as keys.
+ */
+
+#ifndef ECOLI_KEYVAL_
+#define ECOLI_KEYVAL_
+
+#include <stdio.h>
+#include <stdbool.h>
+
+typedef void (*ec_keyval_elt_free_t)(void *);
+
+struct ec_keyval;
+struct ec_keyval_iter;
+
+/**
+ * Create a hash table.
+ *
+ * @return
+ *   The hash table, or NULL on error (errno is set).
+ */
+struct ec_keyval *ec_keyval(void);
+
+/**
+ * Get a value from the hash table.
+ *
+ * @param keyval
+ *   The hash table.
+ * @param key
+ *   The key string.
+ * @return
+ *   The element if it is found, or NULL on error (errno is set).
+ *   In case of success but the element is NULL, errno is set to 0.
+ */
+void *ec_keyval_get(const struct ec_keyval *keyval, const char *key);
+
+/**
+ * Check if the hash table contains this key.
+ *
+ * @param keyval
+ *   The hash table.
+ * @param key
+ *   The key string.
+ * @return
+ *   true if it contains the key, else false.
+ */
+bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key);
+
+/**
+ * Delete an object from the hash table.
+ *
+ * @param keyval
+ *   The hash table.
+ * @param key
+ *   The key string.
+ * @return
+ *   0 on success, or -1 on error (errno is set).
+ */
+int ec_keyval_del(struct ec_keyval *keyval, const char *key);
+
+/**
+ * Add/replace an object in the hash table.
+ *
+ * @param keyval
+ *   The hash table.
+ * @param key
+ *   The key string.
+ * @param val
+ *   The pointer to be saved in the hash table.
+ * @param free_cb
+ *   An optional pointer to a destructor function called when an
+ *   object is destroyed (ec_keyval_del() or ec_keyval_free()).
+ * @return
+ *   0 on success, or -1 on error (errno is set).
+ *   On error, the passed value is freed (free_cb(val) is called).
+ */
+int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val,
+       ec_keyval_elt_free_t free_cb);
+
+/**
+ * Free a hash table an all its objects.
+ *
+ * @param keyval
+ *   The hash table.
+ */
+void ec_keyval_free(struct ec_keyval *keyval);
+
+/**
+ * Get the length of a hash table.
+ *
+ * @param keyval
+ *   The hash table.
+ * @return
+ *   The length of the hash table.
+ */
+size_t ec_keyval_len(const struct ec_keyval *keyval);
+
+/**
+ * Duplicate a hash table
+ *
+ * A reference counter is shared between the clones of
+ * hash tables so that the objects are freed only when
+ * the last reference is destroyed.
+ *
+ * @param keyval
+ *   The hash table.
+ * @return
+ *   The duplicated hash table, or NULL on error (errno is set).
+ */
+struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval);
+
+/**
+ * Dump a hash table.
+ *
+ * @param out
+ *   The stream where the dump is sent.
+ * @param keyval
+ *   The hash table.
+ */
+void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval);
+
+/**
+ * Iterate the elements in the hash table.
+ *
+ * The typical usage is as below:
+ *
+ *     // dump elements
+ *     for (iter = ec_keyval_iter(keyval);
+ *          ec_keyval_iter_valid(iter);
+ *          ec_keyval_iter_next(iter)) {
+ *             printf("  %s: %p\n",
+ *                     ec_keyval_iter_get_key(iter),
+ *                     ec_keyval_iter_get_val(iter));
+ *     }
+ *     ec_keyval_iter_free(iter);
+ *
+ * @param keyval
+ *   The hash table.
+ * @return
+ *   An iterator, or NULL on error (errno is set).
+ */
+struct ec_keyval_iter *
+ec_keyval_iter(const struct ec_keyval *keyval);
+
+/**
+ * Make the iterator point to the next element in the hash table.
+ *
+ * @param iter
+ *   The hash table iterator.
+ */
+void ec_keyval_iter_next(struct ec_keyval_iter *iter);
+
+/**
+ * Free the iterator.
+ *
+ * @param iter
+ *   The hash table iterator.
+ */
+void ec_keyval_iter_free(struct ec_keyval_iter *iter);
+
+/**
+ * Check if the iterator points to a valid element.
+ *
+ * @param iter
+ *   The hash table iterator.
+ * @return
+ *   true if the element is valid, else false.
+ */
+bool
+ec_keyval_iter_valid(const struct ec_keyval_iter *iter);
+
+/**
+ * Get the key of the current element.
+ *
+ * @param iter
+ *   The hash table iterator.
+ * @return
+ *   The current element key, or NULL if the iterator points to an
+ *   invalid element.
+ */
+const char *
+ec_keyval_iter_get_key(const struct ec_keyval_iter *iter);
+
+/**
+ * Get the value of the current element.
+ *
+ * @param iter
+ *   The hash table iterator.
+ * @return
+ *   The current element value, or NULL if the iterator points to an
+ *   invalid element.
+ */
+void *
+ec_keyval_iter_get_val(const struct ec_keyval_iter *iter);
+
+
+#endif
diff --git a/libecoli/ecoli_log.c b/libecoli/ecoli_log.c
new file mode 100644 (file)
index 0000000..e7577cd
--- /dev/null
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#define _GNU_SOURCE /* for vasprintf */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <syslog.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+
+EC_LOG_TYPE_REGISTER(log);
+
+static ec_log_t ec_log_fct = ec_log_default_cb;
+static void *ec_log_opaque;
+
+struct ec_log_type {
+       char *name;
+       enum ec_log_level level;
+};
+
+static struct ec_log_type *log_types;
+static size_t log_types_len;
+static enum ec_log_level global_level = EC_LOG_WARNING;
+
+int ec_log_level_set(enum ec_log_level level)
+{
+       if (level > EC_LOG_DEBUG)
+               return -1;
+       global_level = level;
+
+       return 0;
+}
+
+enum ec_log_level ec_log_level_get(void)
+{
+       return global_level;
+}
+
+int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
+               const char *str)
+{
+       (void)opaque;
+
+       if (level > ec_log_level_get())
+               return 0;
+
+       if (fprintf(stderr, "[%d] %-12s %s", level, ec_log_name(type), str) < 0)
+               return -1;
+
+       return 0;
+}
+
+int ec_log_fct_register(ec_log_t usr_log, void *opaque)
+{
+       if (usr_log == NULL) {
+               ec_log_fct = ec_log_default_cb;
+               ec_log_opaque = NULL;
+       } else {
+               ec_log_fct = usr_log;
+               ec_log_opaque = opaque;
+       }
+
+       return 0;
+}
+
+static int
+ec_log_lookup(const char *name)
+{
+       size_t i;
+
+       for (i = 0; i < log_types_len; i++) {
+               if (log_types[i].name == NULL)
+                       continue;
+               if (strcmp(name, log_types[i].name) == 0)
+                       return i;
+       }
+
+       return -1;
+}
+
+int
+ec_log_type_register(const char *name)
+{
+       struct ec_log_type *new_types;
+       char *copy;
+       int id;
+
+       id = ec_log_lookup(name);
+       if (id >= 0)
+               return id;
+
+       new_types = ec_realloc(log_types,
+               sizeof(*new_types) * (log_types_len + 1));
+       if (new_types == NULL)
+               return -1; /* errno is set */
+       log_types = new_types;
+
+       copy = ec_strdup(name);
+       if (copy == NULL)
+               return -1; /* errno is set */
+
+       id = log_types_len++;
+       log_types[id].name = copy;
+       log_types[id].level = EC_LOG_DEBUG;
+
+       return id;
+}
+
+const char *
+ec_log_name(int type)
+{
+       if (type < 0 || (unsigned int)type >= log_types_len)
+               return "unknown";
+       return log_types[type].name;
+}
+
+int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap)
+{
+       char *s;
+       int ret;
+
+       /* don't use ec_vasprintf here, because it will call
+        * ec_malloc(), then ec_log(), ec_vasprintf()...
+        * -> stack overflow */
+       ret = vasprintf(&s, format, ap);
+       if (ret < 0)
+               return ret;
+
+       ret = ec_log_fct(type, level, ec_log_opaque, s);
+       free(s);
+
+       return ret;
+}
+
+int ec_log(int type, enum ec_log_level level, const char *format, ...)
+{
+       va_list ap;
+       int ret;
+
+       va_start(ap, format);
+       ret = ec_vlog(type, level, format, ap);
+       va_end(ap);
+
+       return ret;
+}
+
+/* LCOV_EXCL_START */
+static int
+log_cb(int type, enum ec_log_level level, void *opaque, const char *str)
+{
+       (void)type;
+       (void)level;
+       (void)str;
+       *(int *)opaque = 1;
+
+       return 0;
+}
+
+static int ec_log_testcase(void)
+{
+       ec_log_t prev_log_cb;
+       void *prev_opaque;
+       const char *logname;
+       int testres = 0;
+       int check_cb = 0;
+       int logtype;
+       int level;
+       int ret;
+
+       prev_log_cb = ec_log_fct;
+       prev_opaque = ec_log_opaque;
+
+       ret = ec_log_fct_register(log_cb, &check_cb);
+       testres |= EC_TEST_CHECK(ret == 0,
+                               "cannot register log function\n");
+       EC_LOG(LOG_ERR, "test\n");
+       testres |= EC_TEST_CHECK(check_cb == 1,
+                               "log callback was not invoked\n");
+       logtype = ec_log_lookup("dsdedesdes");
+       testres |= EC_TEST_CHECK(logtype == -1,
+                               "lookup invalid name should return -1");
+       logtype = ec_log_lookup("log");
+       logname = ec_log_name(logtype);
+       testres |= EC_TEST_CHECK(logname != NULL &&
+                               !strcmp(logname, "log"),
+                               "cannot get log name\n");
+       logname = ec_log_name(-1);
+       testres |= EC_TEST_CHECK(logname != NULL &&
+                               !strcmp(logname, "unknown"),
+                               "cannot get invalid log name\n");
+       logname = ec_log_name(34324);
+       testres |= EC_TEST_CHECK(logname != NULL &&
+                               !strcmp(logname, "unknown"),
+                               "cannot get invalid log name\n");
+       level = ec_log_level_get();
+       ret = ec_log_level_set(2);
+       testres |= EC_TEST_CHECK(ret == 0 && ec_log_level_get() == 2,
+                               "cannot set log level\n");
+       ret = ec_log_level_set(10);
+       testres |= EC_TEST_CHECK(ret != 0,
+                               "should not be able to set log level\n");
+
+       ret = ec_log_fct_register(NULL, NULL);
+       ec_log_level_set(LOG_DEBUG);
+       EC_LOG(LOG_DEBUG, "test log\n");
+       ec_log_level_set(LOG_INFO);
+       EC_LOG(LOG_DEBUG, "test log (not displayed)\n");
+       ec_log_level_set(level);
+
+       ec_log_fct = prev_log_cb;
+       ec_log_opaque = prev_opaque;
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_log_test = {
+       .name = "log",
+       .test = ec_log_testcase,
+};
+
+EC_TEST_REGISTER(ec_log_test);
diff --git a/libecoli/ecoli_log.h b/libecoli/ecoli_log.h
new file mode 100644 (file)
index 0000000..be7e380
--- /dev/null
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Logging API
+ *
+ * This file provide logging helpers:
+ * - logging functions, supporting printf-like format
+ * - several debug level (similar to syslog)
+ * - named log types
+ * - redirection of log to a user functions (default logs nothing)
+ */
+
+#ifndef ECOLI_LOG_
+#define ECOLI_LOG_
+
+#include <stdarg.h>
+
+#include <ecoli_assert.h>
+
+enum ec_log_level {
+       EC_LOG_EMERG   = 0,  /* system is unusable               */
+       EC_LOG_ALERT   = 1,  /* action must be taken immediately */
+       EC_LOG_CRIT    = 2,  /* critical conditions              */
+       EC_LOG_ERR     = 3,  /* error conditions                 */
+       EC_LOG_WARNING = 4,  /* warning conditions               */
+       EC_LOG_NOTICE  = 5,  /* normal but significant condition */
+       EC_LOG_INFO    = 6,  /* informational                    */
+       EC_LOG_DEBUG   = 7,  /* debug-level messages             */
+};
+
+/**
+ * Register a log type.
+ *
+ * This macro defines a function that will be called at startup (using
+ * the "constructor" attribute). This function register the named type
+ * passed as argument, and sets a static global variable
+ * "ec_log_local_type". This variable is used as the default log type
+ * for this file when using EC_LOG() or EC_VLOG().
+ *
+ * This macro can be present several times in a file. In this case, the
+ * local log type is set to the last registered type.
+ *
+ * On error, the function aborts.
+ *
+ * @param name
+ *   The name of the log to be registered.
+ */
+#define EC_LOG_TYPE_REGISTER(name)                                     \
+       static int name##_log_type;                                     \
+       static int ec_log_local_type;                                   \
+       __attribute__((constructor, used))                              \
+       static void ec_log_register_##name(void)                        \
+       {                                                               \
+               ec_log_local_type = ec_log_type_register(#name);        \
+               ec_assert_print(ec_log_local_type >= 0,                 \
+                               "cannot register log type.\n");         \
+               name##_log_type = ec_log_local_type;                    \
+       }
+
+/**
+ * User log function type.
+ *
+ * It is advised that a user-defined log function drops all messages
+ * that are at least as critical as ec_log_level_get(), as done by the
+ * default handler.
+ *
+ * @param type
+ *   The log type identifier.
+ * @param level
+ *   The log level.
+ * @param opaque
+ *   The opaque pointer that was passed to ec_log_fct_register().
+ * @param str
+ *   The string to log.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+typedef int (*ec_log_t)(int type, enum ec_log_level level, void *opaque,
+                       const char *str);
+
+/**
+ * Register a user log function.
+ *
+ * @param usr_log
+ *   Function pointer that will be invoked for each log call.
+ *   If the parameter is NULL, ec_log_default_cb() is used.
+ * @param opaque
+ *   Opaque pointer passed to the log function.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_log_fct_register(ec_log_t usr_log, void *opaque);
+
+/**
+ * Register a named log type.
+ *
+ * Register a new log type, which is identified by its name. The
+ * function returns a log identifier associated to the log name. If the
+ * name is already registered, the function just returns its identifier.
+ *
+ * @param name
+ *   The name of the log type.
+ * @return
+ *   The log type identifier on success (positive or zero), -1 on
+ *   error (errno is set).
+ */
+int ec_log_type_register(const char *name);
+
+/**
+ * Return the log name associated to the log type identifier.
+ *
+ * @param type
+ *   The log type identifier.
+ * @return
+ *   The name associated to the log type, or "unknown". It always return
+ *   a valid string (never NULL).
+ */
+const char *ec_log_name(int type);
+
+/**
+ * Log a formatted string.
+ *
+ * @param type
+ *   The log type identifier.
+ * @param level
+ *   The log level.
+ * @param format
+ *   The format string, followed by optional arguments.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_log(int type, enum ec_log_level level, const char *format, ...)
+       __attribute__((format(__printf__, 3, 4)));
+
+/**
+ * Log a formatted string.
+ *
+ * @param type
+ *   The log type identifier.
+ * @param level
+ *   The log level.
+ * @param format
+ *   The format string.
+ * @param ap
+ *   The list of arguments.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap);
+
+/**
+ * Log a formatted string using the local log type.
+ *
+ * This macro requires that a log type is previously register with
+ * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type"
+ * variable.
+ *
+ * @param level
+ *   The log level.
+ * @param format
+ *   The format string, followed by optional arguments.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+#define EC_LOG(level, args...) ec_log(ec_log_local_type, level, args)
+
+/**
+ * Log a formatted string using the local log type.
+ *
+ * This macro requires that a log type is previously register with
+ * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type"
+ * variable.
+ *
+ * @param level
+ *   The log level.
+ * @param format
+ *   The format string.
+ * @param ap
+ *   The list of arguments.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+#define EC_VLOG(level, fmt, ap) ec_vlog(ec_log_local_type, level, fmt, ap)
+
+/**
+ * Default log handler.
+ *
+ * This is the default log function that is used by the library. By
+ * default, it prints all logs whose level is WARNING or more critical.
+ * This level can be changed with ec_log_level_set().
+ *
+ * @param type
+ *   The log type identifier.
+ * @param level
+ *   The log level.
+ * @param opaque
+ *   Unused.
+ * @param str
+ *   The string to be logged.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
+               const char *str);
+
+/**
+ * Set the global log level.
+ *
+ * This level is used by the default log handler, ec_log_default_cb().
+ * All messages that are at least as critical as the default level are
+ * displayed.
+ *
+ * It is advised
+ *
+ * @param level
+ *   The log level to be set.
+ * @return
+ *   0 on success, -1 on error.
+ */
+int ec_log_level_set(enum ec_log_level level);
+
+/**
+ * Get the global log level.
+ *
+ * This level is used by the default log handler, ec_log_default_cb().
+ * All messages that are at least as critical as the default level are
+ * displayed.
+ *
+ * @param level
+ *   The log level to be set.
+ * @return
+ *   0 on success, -1 on error.
+ */
+enum ec_log_level ec_log_level_get(void);
+
+#endif
diff --git a/libecoli/ecoli_malloc.c b/libecoli/ecoli_malloc.c
new file mode 100644 (file)
index 0000000..505f49f
--- /dev/null
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <ecoli_init.h>
+#include <ecoli_test.h>
+#include <ecoli_malloc.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+EC_LOG_TYPE_REGISTER(malloc);
+
+static int init_done = 0;
+
+struct ec_malloc_handler ec_malloc_handler;
+
+int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free,
+       ec_realloc_t usr_realloc)
+{
+       if (usr_malloc == NULL || usr_free == NULL || usr_realloc == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (init_done) {
+               errno = EBUSY;
+               return -1;
+       }
+
+       ec_malloc_handler.malloc = usr_malloc;
+       ec_malloc_handler.free = usr_free;
+       ec_malloc_handler.realloc = usr_realloc;
+
+       return 0;
+}
+
+void *__ec_malloc(size_t size, const char *file, unsigned int line)
+{
+       return ec_malloc_handler.malloc(size, file, line);
+}
+
+void *ec_malloc_func(size_t size)
+{
+       return __ec_malloc(size, __FILE__, __LINE__);
+}
+
+void __ec_free(void *ptr, const char *file, unsigned int line)
+{
+       ec_malloc_handler.free(ptr, file, line);
+}
+
+void ec_free_func(void *ptr)
+{
+       __ec_free(ptr, __FILE__, __LINE__);
+}
+
+void *__ec_calloc(size_t nmemb, size_t size, const char *file,
+       unsigned int line)
+{
+       void *ptr;
+       size_t total;
+
+       /* check overflow */
+       total = size * nmemb;
+       if (nmemb != 0 && size != (total / nmemb)) {
+               errno = ENOMEM;
+               return NULL;
+       }
+
+       ptr = __ec_malloc(total, file, line);
+       if (ptr == NULL)
+               return NULL;
+
+       memset(ptr, 0, total);
+       return ptr;
+}
+
+void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line)
+{
+       return ec_malloc_handler.realloc(ptr, size, file, line);
+}
+
+char *__ec_strdup(const char *s, const char *file, unsigned int line)
+{
+       size_t sz = strlen(s) + 1;
+       char *s2;
+
+       s2 = __ec_malloc(sz, file, line);
+       if (s2 == NULL)
+               return NULL;
+
+       memcpy(s2, s, sz);
+
+       return s2;
+}
+
+char *__ec_strndup(const char *s, size_t n, const char *file, unsigned int line)
+{
+       size_t sz = strnlen(s, n);
+       char *s2;
+
+       s2 = __ec_malloc(sz + 1, file, line);
+       if (s2 == NULL)
+               return NULL;
+
+       memcpy(s2, s, sz);
+       s2[sz] = '\0';
+
+       return s2;
+}
+
+static int ec_malloc_init_func(void)
+{
+       init_done = 1;
+       return 0;
+}
+
+static struct ec_init ec_malloc_init = {
+       .init = ec_malloc_init_func,
+       .priority = 40,
+};
+
+EC_INIT_REGISTER(ec_malloc_init);
+
+/* LCOV_EXCL_START */
+static int ec_malloc_testcase(void)
+{
+       int ret, testres = 0;
+       char *ptr, *ptr2;
+
+       ret = ec_malloc_register(NULL, NULL, NULL);
+       testres |= EC_TEST_CHECK(ret == -1,
+               "should not be able to register NULL malloc handlers");
+       ret = ec_malloc_register(__ec_malloc, __ec_free, __ec_realloc);
+       testres |= EC_TEST_CHECK(ret == -1,
+               "should not be able to register after init");
+
+       /* registration is tested in the test main.c */
+
+       ptr = ec_malloc(10);
+       if (ptr == NULL)
+               return -1;
+       memset(ptr, 0, 10);
+       ptr2 = ec_realloc(ptr, 20);
+       EC_TEST_CHECK(ptr2 != NULL, "cannot realloc ptr\n");
+       if (ptr2 == NULL)
+               ec_free(ptr);
+       else
+               ec_free(ptr2);
+       ptr = NULL;
+       ptr2 = NULL;
+
+       ptr = ec_malloc_func(10);
+       if (ptr == NULL)
+               return -1;
+       memset(ptr, 0, 10);
+       ec_free_func(ptr);
+       ptr = NULL;
+
+       ptr = ec_calloc(2, (size_t)-1);
+       EC_TEST_CHECK(ptr == NULL, "bad overflow check in ec_calloc\n");
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_malloc_test = {
+       .name = "malloc",
+       .test = ec_malloc_testcase,
+};
+
+EC_TEST_REGISTER(ec_malloc_test);
diff --git a/libecoli/ecoli_malloc.h b/libecoli/ecoli_malloc.h
new file mode 100644 (file)
index 0000000..e80c3d9
--- /dev/null
@@ -0,0 +1,247 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Interface to configure the allocator used by libecoli.
+ * By default, the standard allocation functions from libc are used.
+ */
+
+#ifndef ECOLI_MALLOC_
+#define ECOLI_MALLOC_
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * Function type of malloc, passed to ec_malloc_register().
+ *
+ * The API is the same than malloc(), excepted the file and line
+ * arguments.
+ *
+ * @param size
+ *   The size of the memory area to allocate.
+ * @param file
+ *   The path to the file that invoked the malloc.
+ * @param line
+ *   The line in the file that invoked the malloc.
+ * @return
+ *   A pointer to the allocated memory area, or NULL on error (errno
+ *   is set).
+ */
+typedef void *(*ec_malloc_t)(size_t size, const char *file, unsigned int line);
+
+/**
+ * Function type of free, passed to ec_malloc_register().
+ *
+ * The API is the same than free(), excepted the file and line
+ * arguments.
+ *
+ * @param ptr
+ *   The pointer to the memory area to be freed.
+ * @param file
+ *   The path to the file that invoked the malloc.
+ * @param line
+ *   The line in the file that invoked the malloc.
+ */
+typedef void (*ec_free_t)(void *ptr, const char *file, unsigned int line);
+
+/**
+ * Function type of realloc, passed to ec_malloc_register().
+ *
+ * The API is the same than realloc(), excepted the file and line
+ * arguments.
+ *
+ * @param ptr
+ *   The pointer to the memory area to be reallocated.
+ * @param file
+ *   The path to the file that invoked the malloc.
+ * @param line
+ *   The line in the file that invoked the malloc.
+ * @return
+ *   A pointer to the allocated memory area, or NULL on error (errno
+ *   is set).
+ */
+typedef void *(*ec_realloc_t)(void *ptr, size_t size, const char *file,
+       unsigned int line);
+
+/**
+ * Register allocation functions.
+ *
+ * This function can be use to register another allocator
+ * to be used by libecoli. By default, ec_malloc(), ec_free() and
+ * ec_realloc() use the standard libc allocator. Another handler
+ * can be used for debug purposes or when running in a specific
+ * environment.
+ *
+ * This function must be called before ec_init().
+ *
+ * @param usr_malloc
+ *   A user-defined malloc function.
+ * @param usr_free
+ *   A user-defined free function.
+ * @param usr_realloc
+ *   A user-defined realloc function.
+ * @return
+ *   0 on success, or -1 on error (errno is set).
+ */
+int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free,
+       ec_realloc_t usr_realloc);
+
+struct ec_malloc_handler {
+       ec_malloc_t malloc;
+       ec_free_t free;
+       ec_realloc_t realloc;
+};
+
+extern struct ec_malloc_handler ec_malloc_handler;
+
+/**
+ * Allocate a memory area.
+ *
+ * Like malloc(), ec_malloc() allocates size bytes and returns a pointer
+ * to the allocated memory. The memory is not initialized. The memory is
+ * freed with ec_free().
+ *
+ * @param size
+ *   The size of the area to allocate in bytes.
+ * @return
+ *   The pointer to the allocated memory, or NULL on error (errno is set).
+ */
+#define ec_malloc(size) ({                                             \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.malloc == NULL)                           \
+               ret_ = malloc(size);                                    \
+       else                                                            \
+               ret_ = __ec_malloc(size, __FILE__, __LINE__);           \
+       ret_;                                                           \
+       })
+
+/**
+ * Ecoli malloc function.
+ *
+ * Use this function when the macro ec_malloc() cannot be used,
+ * for instance when it is passed as a callback pointer.
+ */
+void *ec_malloc_func(size_t size);
+
+/**
+ * Free a memory area.
+ *
+ * Like free(), ec_free() frees the area pointed by ptr, which must have
+ * been returned by a previous call to ec_malloc() or any other
+ * allocation function of this file.
+ *
+ * @param ptr
+ *   The pointer to the memory area.
+ */
+#define ec_free(ptr) ({                                                        \
+       if (ec_malloc_handler.free == NULL)                             \
+               free(ptr);                                              \
+       else                                                            \
+               __ec_free(ptr, __FILE__, __LINE__);                     \
+       })
+
+/**
+ * Ecoli free function.
+ *
+ * Use this function when the macro ec_free() cannot be used,
+ * for instance when it is passed as a callback pointer.
+ */
+void ec_free_func(void *ptr);
+
+/**
+ * Resize an allocated memory area.
+ *
+ * @param ptr
+ *   The pointer to the previously allocated memory area, or NULL.
+ * @param size
+ *   The new size of the memory area.
+ * @return
+ *   A pointer to the newly allocated memory, or NULL if the request
+ *   fails. In that case, the original area is left untouched.
+ */
+#define ec_realloc(ptr, size) ({                                       \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.realloc == NULL)                          \
+               ret_ = realloc(ptr, size);                              \
+       else                                                            \
+               ret_ = __ec_realloc(ptr, size, __FILE__, __LINE__);     \
+       ret_;                                                           \
+       })
+
+/**
+ * Allocate and initialize an array of elements.
+ *
+ * @param n
+ *   The number of elements.
+ * @param size
+ *   The size of each element.
+ * @return
+ *   The pointer to the allocated memory, or NULL on error (errno is set).
+ */
+#define ec_calloc(n, size) ({                                          \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.malloc == NULL)                           \
+               ret_ = calloc(n, size);                                 \
+       else                                                            \
+               ret_ = __ec_calloc(n, size, __FILE__, __LINE__);        \
+       ret_;                                                           \
+       })
+
+/**
+ * Duplicate a string.
+ *
+ * Memory for the new string is obtained with ec_malloc(), and can be
+ * freed with ec_free().
+ *
+ * @param s
+ *   The string to be duplicated.
+ * @return
+ *   The pointer to the duplicated string, or NULL on error (errno is set).
+ */
+#define ec_strdup(s) ({                                                        \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.malloc == NULL)                           \
+               ret_ = strdup(s);                                       \
+       else                                                            \
+               ret_ = __ec_strdup(s, __FILE__, __LINE__);              \
+       ret_;                                                           \
+       })
+
+/**
+ * Duplicate at most n bytes of a string.
+ *
+ * This function is similar to ec_strdup(), except that it copies at
+ * most n bytes.  If s is longer than n, only n bytes are copied, and a
+ * terminating null byte ('\0') is added.
+ *
+ * @param s
+ *   The string to be duplicated.
+ * @param n
+ *   The maximum length of the new string.
+ * @return
+ *   The pointer to the duplicated string, or NULL on error (errno is set).
+ */
+#define ec_strndup(s, n) ({                                            \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.malloc == NULL)                           \
+               ret_ = strndup(s, n);                                   \
+       else                                                            \
+               ret_ = __ec_strndup(s, n, __FILE__, __LINE__);          \
+       ret_;                                                           \
+       })
+
+/* internal */
+void *__ec_malloc(size_t size, const char *file, unsigned int line);
+void __ec_free(void *ptr, const char *file, unsigned int line);
+void *__ec_calloc(size_t nmemb, size_t size, const char *file,
+       unsigned int line);
+void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line);
+char *__ec_strdup(const char *s, const char *file, unsigned int line);
+char *__ec_strndup(const char *s, size_t n, const char *file,
+       unsigned int line);
+
+
+#endif
diff --git a/libecoli/ecoli_murmurhash.c b/libecoli/ecoli_murmurhash.c
new file mode 100644 (file)
index 0000000..7aafece
--- /dev/null
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdint.h>
+
+#include <ecoli_murmurhash.h>
+
+uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed)
+{
+       const uint8_t *data = (const uint8_t *)key;
+       const uint8_t *tail;
+       const int nblocks = len / 4;
+       uint32_t h1 = seed;
+       uint32_t k1;
+       const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4);
+       int i;
+
+       for (i = -nblocks; i; i++) {
+               k1 = blocks[i];
+
+               h1 = ec_murmurhash3_add32(h1, k1);
+               h1 = ec_murmurhash3_mix32(h1);
+       }
+
+       tail = (const uint8_t *)(data + nblocks * 4);
+       k1 = 0;
+
+       switch(len & 3) {
+       case 3: k1 ^= tail[2] << 16;
+       case 2: k1 ^= tail[1] << 8;
+       case 1: k1 ^= tail[0];
+               h1 = ec_murmurhash3_add32(h1, k1);
+       };
+
+       /* finalization */
+       h1 ^= len;
+       h1 = ec_murmurhash3_fmix32(h1);
+       return h1;
+}
diff --git a/libecoli/ecoli_murmurhash.h b/libecoli/ecoli_murmurhash.h
new file mode 100644 (file)
index 0000000..6b76d34
--- /dev/null
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * MurmurHash3 is a hash implementation that was written by Austin Appleby, and
+ * is placed in the public domain. The author hereby disclaims copyright to this
+ * source code.
+ */
+
+#ifndef ECOLI_MURMURHASH_H_
+#define ECOLI_MURMURHASH_H_
+
+#include <stdint.h>
+
+/** Hash rotation */
+static inline uint32_t ec_murmurhash_rotl32(uint32_t x, int8_t r)
+{
+       return (x << r) | (x >> (32 - r));
+}
+
+/** Add 32-bit to the hash */
+static inline uint32_t ec_murmurhash3_add32(uint32_t h, uint32_t data)
+{
+       data *= 0xcc9e2d51;
+       data = ec_murmurhash_rotl32(data, 15);
+       data *= 0x1b873593;
+       h ^= data;
+       return h;
+}
+
+/** Intermediate mix */
+static inline uint32_t ec_murmurhash3_mix32(uint32_t h)
+{
+       h = ec_murmurhash_rotl32(h,13);
+       h = h * 5 +0xe6546b64;
+       return h;
+}
+
+/** Final mix: force all bits of a hash block to avalanche */
+static inline uint32_t ec_murmurhash3_fmix32(uint32_t h)
+{
+       h ^= h >> 16;
+       h *= 0x85ebca6b;
+       h ^= h >> 13;
+       h *= 0xc2b2ae35;
+       h ^= h >> 16;
+
+       return h;
+}
+
+/**
+ * Calculate a 32-bit murmurhash3
+ *
+ * @param key
+ *   The key (the unaligned variable-length array of bytes).
+ * @param len
+ *   The length of the key, counting by bytes.
+ * @param seed
+ *   Can be any 4-byte value initialization value.
+ * @return
+ *   A 32-bit hash.
+ */
+uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed);
+
+#endif /* ECOLI_MURMURHASH_H_ */
diff --git a/libecoli/ecoli_node.c b/libecoli/ecoli_node.c
new file mode 100644 (file)
index 0000000..c02a8de
--- /dev/null
@@ -0,0 +1,607 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_log.h>
+#include <ecoli_config.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+
+#include <ecoli_node_str.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_int.h>
+
+EC_LOG_TYPE_REGISTER(node);
+
+static struct ec_node_type_list node_type_list =
+       TAILQ_HEAD_INITIALIZER(node_type_list);
+
+const struct ec_node_type *
+ec_node_type_lookup(const char *name)
+{
+       struct ec_node_type *type;
+
+       TAILQ_FOREACH(type, &node_type_list, next) {
+               if (!strcmp(name, type->name))
+                       return type;
+       }
+
+       errno = ENOENT;
+       return NULL;
+}
+
+int ec_node_type_register(struct ec_node_type *type)
+{
+       EC_CHECK_ARG(type->size >= sizeof(struct ec_node), -1, EINVAL);
+
+       if (ec_node_type_lookup(type->name) != NULL) {
+               errno = EEXIST;
+               return -1;
+       }
+
+       TAILQ_INSERT_TAIL(&node_type_list, type, next);
+
+       return 0;
+}
+
+void ec_node_type_dump(FILE *out)
+{
+       struct ec_node_type *type;
+
+       TAILQ_FOREACH(type, &node_type_list, next)
+               fprintf(out, "%s\n", type->name);
+}
+
+struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id)
+{
+       struct ec_node *node = NULL;
+
+       EC_LOG(EC_LOG_DEBUG, "create node type=%s id=%s\n",
+               type->name, id);
+       if (id == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       node = ec_calloc(1, type->size);
+       if (node == NULL)
+               goto fail;
+
+       node->type = type;
+       node->refcnt = 1;
+
+       node->id = ec_strdup(id);
+       if (node->id == NULL)
+               goto fail;
+
+       if (ec_asprintf(&node->desc, "<%s>", type->name) < 0)
+               goto fail;
+
+       node->attrs = ec_keyval();
+       if (node->attrs == NULL)
+               goto fail;
+
+       if (type->init_priv != NULL) {
+               if (type->init_priv(node) < 0)
+                       goto fail;
+       }
+
+       return node;
+
+ fail:
+       if (node != NULL) {
+               ec_keyval_free(node->attrs);
+               ec_free(node->desc);
+               ec_free(node->id);
+       }
+       ec_free(node);
+
+       return NULL;
+}
+
+const struct ec_config_schema *
+ec_node_type_schema(const struct ec_node_type *type)
+{
+       return type->schema;
+}
+
+const char *
+ec_node_type_name(const struct ec_node_type *type)
+{
+       return type->name;
+}
+
+struct ec_node *ec_node(const char *typename, const char *id)
+{
+       const struct ec_node_type *type;
+
+       type = ec_node_type_lookup(typename);
+       if (type == NULL) {
+               EC_LOG(EC_LOG_ERR, "type=%s does not exist\n",
+                       typename);
+               return NULL;
+       }
+
+       return ec_node_from_type(type, id);
+}
+
+static void count_references(struct ec_node *node, unsigned int refs)
+{
+       struct ec_node *child;
+       size_t i, n;
+       int ret;
+
+       if (node->free.state == EC_NODE_FREE_STATE_TRAVERSED) {
+               node->free.refcnt += refs;
+               return;
+       }
+       node->free.refcnt = refs;
+       node->free.state = EC_NODE_FREE_STATE_TRAVERSED;
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               count_references(child, refs);
+       }
+}
+
+static void mark_freeable(struct ec_node *node, enum ec_node_free_state mark)
+{
+       struct ec_node *child;
+       unsigned int refs;
+       size_t i, n;
+       int ret;
+
+       if (mark == node->free.state)
+               return;
+
+       if (node->refcnt > node->free.refcnt)
+               mark = EC_NODE_FREE_STATE_NOT_FREEABLE;
+       assert(node->refcnt >= node->free.refcnt);
+       node->free.state = mark;
+
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               mark_freeable(child, mark);
+       }
+}
+
+static void reset_mark(struct ec_node *node)
+{
+       struct ec_node *child;
+       unsigned int refs;
+       size_t i, n;
+       int ret;
+
+       if (node->free.state == EC_NODE_FREE_STATE_NONE)
+               return;
+
+       node->free.state = EC_NODE_FREE_STATE_NONE;
+       node->free.refcnt = 0;
+
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               reset_mark(child);
+       }
+}
+
+/* free a node, taking care of loops in the node graph */
+void ec_node_free(struct ec_node *node)
+{
+       size_t n;
+
+       if (node == NULL)
+               return;
+
+       assert(node->refcnt > 0);
+
+       if (node->free.state == EC_NODE_FREE_STATE_NONE &&
+                       node->refcnt != 1) {
+
+               /* Traverse the node tree starting from this node, and for each
+                * node, count the number of reachable references. Then, all
+                * nodes whose reachable references == total reference are
+                * marked as freeable, and other are marked as unfreeable. Any
+                * node reachable from an unfreeable node is also marked as
+                * unfreeable. */
+               if (node->free.state == EC_NODE_FREE_STATE_NONE) {
+                       count_references(node, 1);
+                       mark_freeable(node, EC_NODE_FREE_STATE_FREEABLE);
+               }
+       }
+
+       if (node->free.state == EC_NODE_FREE_STATE_NOT_FREEABLE) {
+               node->refcnt--;
+               reset_mark(node);
+               return;
+       }
+
+       if (node->free.state != EC_NODE_FREE_STATE_FREEING) {
+               node->free.state = EC_NODE_FREE_STATE_FREEING;
+
+               /* children will be freed by config_free() and free_priv() */
+               ec_config_free(node->config);
+               node->config = NULL;
+               n = ec_node_get_children_count(node);
+               assert(n == 0 || node->type->free_priv != NULL);
+               if (node->type->free_priv != NULL)
+                       node->type->free_priv(node);
+               ec_free(node->id);
+               ec_free(node->desc);
+               ec_keyval_free(node->attrs);
+       }
+
+       node->refcnt--;
+       if (node->refcnt != 0)
+               return;
+
+       node->free.state = EC_NODE_FREE_STATE_NONE;
+       node->free.refcnt = 0;
+
+       ec_free(node);
+}
+
+struct ec_node *ec_node_clone(struct ec_node *node)
+{
+       if (node != NULL)
+               node->refcnt++;
+       return node;
+}
+
+size_t ec_node_get_children_count(const struct ec_node *node)
+{
+       if (node->type->get_children_count == NULL)
+               return 0;
+       return node->type->get_children_count(node);
+}
+
+int
+ec_node_get_child(const struct ec_node *node, size_t i,
+       struct ec_node **child, unsigned int *refs)
+{
+       *child = NULL;
+       *refs = 0;
+       if (node->type->get_child == NULL)
+               return -1;
+       return node->type->get_child(node, i, child, refs);
+}
+
+int
+ec_node_set_config(struct ec_node *node, struct ec_config *config)
+{
+       if (node->type->schema == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+       if (ec_config_validate(config, node->type->schema) < 0)
+               goto fail;
+       if (node->type->set_config != NULL) {
+               if (node->type->set_config(node, config) < 0)
+                       goto fail;
+       }
+
+       ec_config_free(node->config);
+       node->config = config;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       return -1;
+}
+
+const struct ec_config *ec_node_get_config(struct ec_node *node)
+{
+       return node->config;
+}
+
+struct ec_node *ec_node_find(struct ec_node *node, const char *id)
+{
+       struct ec_node *child, *retnode;
+       const char *node_id = ec_node_id(node);
+       unsigned int refs;
+       size_t i, n;
+       int ret;
+
+       if (id != NULL && node_id != NULL && !strcmp(node_id, id))
+               return node;
+
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               retnode = ec_node_find(child, id);
+               if (retnode != NULL)
+                       return retnode;
+       }
+
+       return NULL;
+}
+
+const struct ec_node_type *ec_node_type(const struct ec_node *node)
+{
+       return node->type;
+}
+
+struct ec_keyval *ec_node_attrs(const struct ec_node *node)
+{
+       return node->attrs;
+}
+
+const char *ec_node_id(const struct ec_node *node)
+{
+       return node->id;
+}
+
+static void __ec_node_dump(FILE *out,
+       const struct ec_node *node, size_t indent, struct ec_keyval *dict)
+{
+       const char *id, *typename;
+       struct ec_node *child;
+       unsigned int refs;
+       char buf[32];
+       size_t i, n;
+       int ret;
+
+       id = ec_node_id(node);
+       typename = node->type->name;
+
+       snprintf(buf, sizeof(buf), "%p", node);
+       if (ec_keyval_has_key(dict, buf)) {
+               fprintf(out, "%*s" "type=%s id=%s %p... (loop)\n",
+                       (int)indent * 4, "", typename, id, node);
+               return;
+       }
+
+       ec_keyval_set(dict, buf, NULL, NULL);
+       fprintf(out, "%*s" "type=%s id=%s %p refs=%u free_state=%d free_refs=%d\n",
+               (int)indent * 4, "", typename, id, node, node->refcnt,
+               node->free.state, node->free.refcnt);
+
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               __ec_node_dump(out, child, indent + 1, dict);
+       }
+}
+
+/* XXX this is too much debug-oriented, we should have a parameter or 2 funcs */
+void ec_node_dump(FILE *out, const struct ec_node *node)
+{
+       struct ec_keyval *dict = NULL;
+
+       fprintf(out, "------------------- node dump:\n");
+
+       if (node == NULL) {
+               fprintf(out, "node is NULL\n");
+               return;
+       }
+
+       dict = ec_keyval();
+       if (dict == NULL)
+               goto fail;
+
+       __ec_node_dump(out, node, 0, dict);
+
+       ec_keyval_free(dict);
+       return;
+
+fail:
+       ec_keyval_free(dict);
+       EC_LOG(EC_LOG_ERR, "failed to dump node\n");
+}
+
+const char *ec_node_desc(const struct ec_node *node)
+{
+       if (node->type->desc != NULL)
+               return node->type->desc(node);
+
+       return node->desc;
+}
+
+int ec_node_check_type(const struct ec_node *node,
+               const struct ec_node_type *type)
+{
+       if (strcmp(node->type->name, type->name)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       return 0;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_testcase(void)
+{
+       struct ec_node *node = NULL, *expr = NULL;
+       struct ec_node *expr2 = NULL, *val = NULL, *op = NULL, *seq = NULL;
+       const struct ec_node_type *type;
+       struct ec_node *child;
+       unsigned int refs;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+       int testres = 0;
+       int ret;
+
+       node = EC_NODE_SEQ(EC_NO_ID,
+                       ec_node_str("id_x", "x"),
+                       ec_node_str("id_y", "y"));
+       if (node == NULL)
+               goto fail;
+
+       ec_node_clone(node);
+       ec_node_free(node);
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_node_dump(f, node);
+       ec_node_type_dump(f);
+       ec_node_dump(f, NULL);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "type=seq id=no-id"), "bad dump\n");
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "type=str id=id_x") &&
+               strstr(strstr(buf, "type=str id=id_x") + 1,
+                       "type=str id=id_y"),
+               "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_node_type(node)->name, "seq") &&
+               !strcmp(ec_node_id(node), EC_NO_ID) &&
+               !strcmp(ec_node_desc(node), "<seq>"),
+               "bad child 0");
+
+       testres |= EC_TEST_CHECK(
+               ec_node_get_children_count(node) == 2,
+               "bad children count\n");
+       ret = ec_node_get_child(node, 0, &child, &refs);
+       testres |= EC_TEST_CHECK(ret == 0 &&
+               child != NULL &&
+               !strcmp(ec_node_type(child)->name, "str") &&
+               !strcmp(ec_node_id(child), "id_x"),
+               "bad child 0");
+       ret = ec_node_get_child(node, 1, &child, &refs);
+       testres |= EC_TEST_CHECK(ret == 0 &&
+               child != NULL &&
+               !strcmp(ec_node_type(child)->name, "str") &&
+               !strcmp(ec_node_id(child), "id_y"),
+               "bad child 1");
+       ret = ec_node_get_child(node, 2, &child, &refs);
+       testres |= EC_TEST_CHECK(ret != 0,
+               "ret should be != 0");
+       testres |= EC_TEST_CHECK(child == NULL,
+               "child 2 should be NULL");
+
+       child = ec_node_find(node, "id_x");
+       testres |= EC_TEST_CHECK(child != NULL &&
+               !strcmp(ec_node_type(child)->name, "str") &&
+               !strcmp(ec_node_id(child), "id_x") &&
+               !strcmp(ec_node_desc(child), "x"),
+               "bad child id_x");
+       child = ec_node_find(node, "id_dezdex");
+       testres |= EC_TEST_CHECK(child == NULL,
+               "child with wrong id should be NULL");
+
+       ret = ec_keyval_set(ec_node_attrs(node), "key", "val", NULL);
+       testres |= EC_TEST_CHECK(ret == 0,
+               "cannot set node attribute\n");
+
+       type = ec_node_type_lookup("seq");
+       testres |= EC_TEST_CHECK(type != NULL &&
+               ec_node_check_type(node, type) == 0,
+               "cannot get seq node type");
+       type = ec_node_type_lookup("str");
+       testres |= EC_TEST_CHECK(type != NULL &&
+               ec_node_check_type(node, type) < 0,
+               "node type should not be str");
+
+       ec_node_free(node);
+       node = NULL;
+
+       node = ec_node("deznuindez", EC_NO_ID);
+       testres |= EC_TEST_CHECK(node == NULL,
+                       "should not be able to create node\n");
+
+       /* test loop */
+       expr = ec_node("or", EC_NO_ID);
+       val = ec_node_int(EC_NO_ID, 0, 10, 0);
+       op = ec_node_str(EC_NO_ID, "!");
+       seq = EC_NODE_SEQ(EC_NO_ID,
+                       op,
+                       ec_node_clone(expr));
+       op = NULL;
+       if (expr == NULL || val == NULL || seq == NULL)
+               goto fail;
+       if (ec_node_or_add(expr, ec_node_clone(seq)) < 0)
+               goto fail;
+       ec_node_free(seq);
+       seq = NULL;
+       if (ec_node_or_add(expr, ec_node_clone(val)) < 0)
+               goto fail;
+       ec_node_free(val);
+       val = NULL;
+
+       testres |= EC_TEST_CHECK_PARSE(expr, 1, "1");
+       testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1");
+       testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!");
+
+       ec_node_free(expr);
+       expr = NULL;
+
+       /* same loop test, but keep some refs (released later) */
+       expr = ec_node("or", EC_NO_ID);
+       ec_node_clone(expr);
+       expr2 = expr;
+       val = ec_node_int(EC_NO_ID, 0, 10, 0);
+       op = ec_node_str(EC_NO_ID, "!");
+       seq = EC_NODE_SEQ(EC_NO_ID,
+                       op,
+                       ec_node_clone(expr));
+       op = NULL;
+       if (expr == NULL || val == NULL || seq == NULL)
+               goto fail;
+       if (ec_node_or_add(expr, ec_node_clone(seq)) < 0)
+               goto fail;
+       ec_node_free(seq);
+       seq = NULL;
+       if (ec_node_or_add(expr, ec_node_clone(val)) < 0)
+               goto fail;
+
+       testres |= EC_TEST_CHECK_PARSE(expr, 1, "1");
+       testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1");
+       testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!");
+
+       ec_node_free(expr2);
+       expr2 = NULL;
+       ec_node_free(val);
+       val = NULL;
+       ec_node_free(expr);
+       expr = NULL;
+
+       return testres;
+
+fail:
+       ec_node_free(expr);
+       ec_node_free(expr2);
+       ec_node_free(val);
+       ec_node_free(seq);
+       ec_node_free(node);
+       if (f != NULL)
+               fclose(f);
+       free(buf);
+
+       assert(errno != 0);
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_test = {
+       .name = "node",
+       .test = ec_node_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_test);
diff --git a/libecoli/ecoli_node.h b/libecoli/ecoli_node.h
new file mode 100644 (file)
index 0000000..45f3e74
--- /dev/null
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Interface to manage the ecoli nodes.
+ *
+ * A node is a main structure of the ecoli library, used to define how
+ * to match and complete the input tokens. A node is a generic object
+ * that implements:
+ * - a parse(node, input) method: check if an input matches
+ * - a complete(node, input) method: return possible completions for
+ *   a given input
+ * - some other methods to initialize, free, ...
+ *
+ * One basic example is the string node (ec_node_str). A node
+ * ec_node_str("foo") will match any token list starting with "foo",
+ * for example:
+ * - ["foo"]
+ * - ["foo", "bar", ...]
+ * But will not match:
+ * - []
+ * - ["bar", ...]
+ *
+ * A node ec_node_str("foo") will complete with "foo" if the input
+ * contains one token, with the same beginning than "foo":
+ * - [""]
+ * - ["f"]
+ * - ["fo"]
+ * - ["foo"]
+ * But it will not complete:
+ * - []
+ * - ["bar"]
+ * - ["f", ""]
+ * - ["", "f"]
+ *
+ * A node can have child nodes. For instance, a sequence node
+ * ec_node_seq(ec_node_str("foo"), ec_node_str("bar")) will match
+ * ["foo", "bar"].
+ */
+
+#ifndef ECOLI_NODE_
+#define ECOLI_NODE_
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#define EC_NO_ID "no-id"
+
+#define EC_NODE_ENDLIST ((void *)1)
+
+struct ec_node;
+struct ec_parse;
+struct ec_comp;
+struct ec_strvec;
+struct ec_keyval;
+struct ec_config;
+struct ec_config_schema;
+
+#define EC_NODE_TYPE_REGISTER(t)                                       \
+       static void ec_node_init_##t(void);                             \
+       static void __attribute__((constructor, used))                  \
+       ec_node_init_##t(void)                                          \
+       {                                                               \
+               if (ec_node_type_register(&t) < 0)                      \
+                       fprintf(stderr,                                 \
+                               "cannot register node type %s\n",       \
+                               t.name);                                \
+       }
+
+TAILQ_HEAD(ec_node_type_list, ec_node_type);
+
+typedef int (*ec_node_set_config_t)(struct ec_node *node,
+                               const struct ec_config *config);
+typedef int (*ec_node_parse_t)(const struct ec_node *node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec);
+typedef int (*ec_node_complete_t)(const struct ec_node *node,
+                               struct ec_comp *comp_state,
+                               const struct ec_strvec *strvec);
+typedef const char * (*ec_node_desc_t)(const struct ec_node *);
+typedef int (*ec_node_init_priv_t)(struct ec_node *);
+typedef void (*ec_node_free_priv_t)(struct ec_node *);
+typedef size_t (*ec_node_get_children_count_t)(const struct ec_node *);
+typedef int (*ec_node_get_child_t)(const struct ec_node *,
+       size_t i, struct ec_node **child, unsigned int *refs);
+
+/**
+ * A structure describing a node type.
+ */
+struct ec_node_type {
+       TAILQ_ENTRY(ec_node_type) next;  /**< Next in list. */
+       const char *name;                /**< Node type name. */
+       /** Configuration schema array, must be terminated by a sentinel
+        *  (.type = EC_CONFIG_TYPE_NONE). */
+       const struct ec_config_schema *schema;
+       ec_node_set_config_t set_config; /* validate/ack a config change */
+       ec_node_parse_t parse;
+       ec_node_complete_t complete;
+       ec_node_desc_t desc;
+       size_t size;
+       ec_node_init_priv_t init_priv;
+       ec_node_free_priv_t free_priv;
+       ec_node_get_children_count_t get_children_count;
+       ec_node_get_child_t get_child;
+};
+
+/**
+ * Register a node type.
+ *
+ * @param type
+ *   A pointer to a ec_test structure describing the test
+ *   to be registered.
+ * @return
+ *   0 on success, negative value on error.
+ */
+int ec_node_type_register(struct ec_node_type *type);
+
+/**
+ * Lookup node type by name
+ *
+ * @param name
+ *   The name of the node type to search.
+ * @return
+ *   The node type if found, or NULL on error.
+ */
+const struct ec_node_type *ec_node_type_lookup(const char *name);
+
+/**
+ * Dump registered log types
+ */
+void ec_node_type_dump(FILE *out);
+
+/**
+ * Get the config schema of a node type.
+ */
+const struct ec_config_schema *
+ec_node_type_schema(const struct ec_node_type *type);
+
+/**
+ * Get the name of a node type.
+ */
+const char *
+ec_node_type_name(const struct ec_node_type *type);
+
+enum ec_node_free_state {
+       EC_NODE_FREE_STATE_NONE,
+       EC_NODE_FREE_STATE_TRAVERSED,
+       EC_NODE_FREE_STATE_FREEABLE,
+       EC_NODE_FREE_STATE_NOT_FREEABLE,
+       EC_NODE_FREE_STATE_FREEING,
+};
+
+struct ec_node {
+       const struct ec_node_type *type;
+       struct ec_config *config;    /**< Generic configuration. */
+       char *id;
+       char *desc;
+       struct ec_keyval *attrs;
+       unsigned int refcnt;
+       struct {
+               enum ec_node_free_state state; /**< State of loop detection */
+               unsigned int refcnt;    /**< Number of reachable references
+                                        *   starting from node beeing freed */
+       } free; /**< Freeing state: used for loop detection */
+};
+
+/* create a new node when the type is known, typically called from the node
+ * code */
+struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id);
+
+/* create a new node */
+struct ec_node *ec_node(const char *typename, const char *id);
+
+struct ec_node *ec_node_clone(struct ec_node *node);
+void ec_node_free(struct ec_node *node);
+
+/* set configuration of a node
+ * after a call to this function, the config is
+ * owned by the node and must not be used by the caller
+ * on error, the config is freed. */
+int ec_node_set_config(struct ec_node *node, struct ec_config *config);
+
+/* get the current node configuration. Return NULL if no configuration. */
+const struct ec_config *ec_node_get_config(struct ec_node *node);
+
+size_t ec_node_get_children_count(const struct ec_node *node);
+int
+ec_node_get_child(const struct ec_node *node, size_t i,
+       struct ec_node **child, unsigned int *refs);
+
+/* XXX add more accessors */
+const struct ec_node_type *ec_node_type(const struct ec_node *node);
+struct ec_keyval *ec_node_attrs(const struct ec_node *node);
+const char *ec_node_id(const struct ec_node *node);
+const char *ec_node_desc(const struct ec_node *node);
+
+void ec_node_dump(FILE *out, const struct ec_node *node);
+struct ec_node *ec_node_find(struct ec_node *node, const char *id);
+
+/* check the type of a node */
+int ec_node_check_type(const struct ec_node *node,
+               const struct ec_node_type *type);
+
+#endif
diff --git a/libecoli/ecoli_node_any.c b/libecoli/ecoli_node_any.c
new file mode 100644 (file)
index 0000000..197a2c5
--- /dev/null
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_any.h>
+
+EC_LOG_TYPE_REGISTER(node_any);
+
+struct ec_node_any {
+       struct ec_node gen;
+};
+
+static int ec_node_any_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+static struct ec_node_type ec_node_any_type = {
+       .name = "any",
+       .parse = ec_node_any_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_any),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_any_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_any_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("any", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+       ec_node_free(node);
+
+       /* never completes */
+       node = ec_node("any", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_any_test = {
+       .name = "node_any",
+       .test = ec_node_any_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_any_test);
diff --git a/libecoli/ecoli_node_any.h b/libecoli/ecoli_node_any.h
new file mode 100644 (file)
index 0000000..ee638aa
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * This node always matches 1 string in the vector
+ */
+
+#ifndef ECOLI_NODE_ANY_
+#define ECOLI_NODE_ANY_
+
+/* no specific API for this node */
+
+#endif
diff --git a/libecoli/ecoli_node_cmd.c b/libecoli/ecoli_node_cmd.c
new file mode 100644 (file)
index 0000000..c61b759
--- /dev/null
@@ -0,0 +1,682 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/queue.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_helper.h>
+#include <ecoli_node_expr.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_subset.h>
+#include <ecoli_node_int.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_option.h>
+#include <ecoli_node_re.h>
+#include <ecoli_node_re_lex.h>
+#include <ecoli_node_cmd.h>
+
+EC_LOG_TYPE_REGISTER(node_cmd);
+
+struct ec_node_cmd {
+       struct ec_node gen;
+       char *cmd_str;           /* the command string. */
+       struct ec_node *cmd;     /* the command node. */
+       struct ec_node *parser;  /* the expression parser. */
+       struct ec_node *expr;    /* the expression parser without lexer. */
+       struct ec_node **table;  /* table of node referenced in command. */
+       unsigned int len;        /* len of the table. */
+};
+
+/* passed as user context to expression parser */
+struct ec_node_cmd_ctx {
+       struct ec_node **table;
+       unsigned int len;
+};
+
+static int
+ec_node_cmd_eval_var(void **result, void *userctx,
+       const struct ec_parse *var)
+{
+       const struct ec_strvec *vec;
+       struct ec_node_cmd_ctx *ctx = userctx;
+       struct ec_node *eval = NULL;
+       const char *str, *id;
+       unsigned int i;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(var);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+       str = ec_strvec_val(vec, 0);
+
+       for (i = 0; i < ctx->len; i++) {
+               id = ec_node_id(ctx->table[i]);
+               if (id == NULL)
+                       continue;
+               if (strcmp(str, id))
+                       continue;
+               /* if id matches, use a node provided by the user... */
+               eval = ec_node_clone(ctx->table[i]);
+               if (eval == NULL)
+                       return -1;
+               break;
+       }
+
+       /* ...or create a string node */
+       if (eval == NULL) {
+               eval = ec_node_str(EC_NO_ID, str);
+               if (eval == NULL)
+                       return -1;
+       }
+
+       *result = eval;
+
+       return 0;
+}
+
+static int
+ec_node_cmd_eval_pre_op(void **result, void *userctx, void *operand,
+       const struct ec_parse *operator)
+{
+       (void)result;
+       (void)userctx;
+       (void)operand;
+       (void)operator;
+
+       errno = EINVAL;
+       return -1;
+}
+
+static int
+ec_node_cmd_eval_post_op(void **result, void *userctx, void *operand,
+       const struct ec_parse *operator)
+{
+       const struct ec_strvec *vec;
+       struct ec_node *in = operand;;
+       struct ec_node *out = NULL;;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "*")) {
+               out = ec_node_many(EC_NO_ID,
+                               ec_node_clone(in), 0, 0);
+               if (out == NULL)
+                       return -1;
+               ec_node_free(in);
+               *result = out;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       return 0;
+}
+
+static int
+ec_node_cmd_eval_bin_op(void **result, void *userctx, void *operand1,
+       const struct ec_parse *operator, void *operand2)
+
+{
+       const struct ec_strvec *vec;
+       struct ec_node *out = NULL;
+       struct ec_node *in1 = operand1;
+       struct ec_node *in2 = operand2;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) > 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (ec_strvec_len(vec) == 0) {
+               if (!strcmp(in1->type->name, "seq")) {
+                       if (ec_node_seq_add(in1, ec_node_clone(in2)) < 0)
+                               return -1;
+                       ec_node_free(in2);
+                       *result = in1;
+               } else {
+                       out = EC_NODE_SEQ(EC_NO_ID, ec_node_clone(in1),
+                                       ec_node_clone(in2));
+                       if (out == NULL)
+                               return -1;
+                       ec_node_free(in1);
+                       ec_node_free(in2);
+                       *result = out;
+               }
+       } else if (!strcmp(ec_strvec_val(vec, 0), "|")) {
+               if (!strcmp(in2->type->name, "or")) {
+                       if (ec_node_or_add(in2, ec_node_clone(in1)) < 0)
+                               return -1;
+                       ec_node_free(in1);
+                       *result = in2;
+               } else if (!strcmp(in1->type->name, "or")) {
+                       if (ec_node_or_add(in1, ec_node_clone(in2)) < 0)
+                               return -1;
+                       ec_node_free(in2);
+                       *result = in1;
+               } else {
+                       out = EC_NODE_OR(EC_NO_ID, ec_node_clone(in1),
+                                       ec_node_clone(in2));
+                       if (out == NULL)
+                               return -1;
+                       ec_node_free(in1);
+                       ec_node_free(in2);
+                       *result = out;
+               }
+       } else if (!strcmp(ec_strvec_val(vec, 0), ",")) {
+               if (!strcmp(in2->type->name, "subset")) {
+                       if (ec_node_subset_add(in2, ec_node_clone(in1)) < 0)
+                               return -1;
+                       ec_node_free(in1);
+                       *result = in2;
+               } else if (!strcmp(in1->type->name, "subset")) {
+                       if (ec_node_subset_add(in1, ec_node_clone(in2)) < 0)
+                               return -1;
+                       ec_node_free(in2);
+                       *result = in1;
+               } else {
+                       out = EC_NODE_SUBSET(EC_NO_ID, ec_node_clone(in1),
+                                       ec_node_clone(in2));
+                       if (out == NULL)
+                               return -1;
+                       ec_node_free(in1);
+                       ec_node_free(in2);
+                       *result = out;
+               }
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       return 0;
+}
+
+static int
+ec_node_cmd_eval_parenthesis(void **result, void *userctx,
+       const struct ec_parse *open_paren,
+       const struct ec_parse *close_paren,
+       void *value)
+{
+       const struct ec_strvec *vec;
+       struct ec_node *in = value;;
+       struct ec_node *out = NULL;;
+
+       (void)userctx;
+       (void)close_paren;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(open_paren);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "[")) {
+               out = ec_node_option(EC_NO_ID, ec_node_clone(in));
+               if (out == NULL)
+                       return -1;
+               ec_node_free(in);
+       } else if (!strcmp(ec_strvec_val(vec, 0), "(")) {
+               out = in;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       *result = out;
+
+       return 0;
+}
+
+static void
+ec_node_cmd_eval_free(void *result, void *userctx)
+{
+       (void)userctx;
+       ec_free(result);
+}
+
+static const struct ec_node_expr_eval_ops expr_ops = {
+       .eval_var = ec_node_cmd_eval_var,
+       .eval_pre_op = ec_node_cmd_eval_pre_op,
+       .eval_post_op = ec_node_cmd_eval_post_op,
+       .eval_bin_op = ec_node_cmd_eval_bin_op,
+       .eval_parenthesis = ec_node_cmd_eval_parenthesis,
+       .eval_free = ec_node_cmd_eval_free,
+};
+
+static struct ec_node *
+ec_node_cmd_build_expr(void)
+{
+       struct ec_node *expr = NULL;
+       int ret;
+
+       /* build the expression parser */
+       expr = ec_node("expr", "expr");
+       if (expr == NULL)
+               goto fail;
+       ret = ec_node_expr_set_val_node(expr, ec_node_re(EC_NO_ID,
+                                       "[a-zA-Z0-9]+"));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, ","));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, "|"));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_bin_op(expr, ec_node("empty", EC_NO_ID));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "+"));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "*"));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "["),
+               ec_node_str(EC_NO_ID, "]"));
+       if (ret < 0)
+               goto fail;
+       ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "("),
+               ec_node_str(EC_NO_ID, ")"));
+       if (ret < 0)
+               goto fail;
+
+       return expr;
+
+fail:
+       ec_node_free(expr);
+       return NULL;
+}
+
+static struct ec_node *
+ec_node_cmd_build_parser(struct ec_node *expr)
+{
+       struct ec_node *lex = NULL;
+       int ret;
+
+       /* prepend a lexer to the expression node */
+       lex = ec_node_re_lex(EC_NO_ID, ec_node_clone(expr));
+       if (lex == NULL)
+               goto fail;
+
+       ret = ec_node_re_lex_add(lex, "[a-zA-Z0-9]+", 1);
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_re_lex_add(lex, "[*|,()]", 1);
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_re_lex_add(lex, "\\[", 1);
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_re_lex_add(lex, "\\]", 1);
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_re_lex_add(lex, "[         ]+", 0);
+       if (ret < 0)
+               goto fail;
+
+       return lex;
+
+fail:
+       ec_node_free(lex);
+
+       return NULL;
+}
+
+static struct ec_node *
+ec_node_cmd_build(struct ec_node_cmd *node, const char *cmd_str,
+       struct ec_node **table, size_t len)
+{
+       struct ec_node_cmd_ctx ctx = { table, len };
+       struct ec_parse *p = NULL;
+       void *result;
+       int ret;
+
+       /* parse the command expression */
+       p = ec_node_parse(node->parser, cmd_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;
+       }
+
+       ret = ec_node_expr_eval(&result, node->expr,
+                               ec_parse_get_first_child(p),
+                               &expr_ops, &ctx);
+       if (ret < 0)
+               goto fail;
+
+       ec_parse_free(p);
+       return result;
+
+fail:
+       ec_parse_free(p);
+       return NULL;
+}
+
+static int
+ec_node_cmd_parse(const struct ec_node *gen_node, struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+
+       return ec_node_parse_child(node->cmd, state, strvec);
+}
+
+static int
+ec_node_cmd_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+
+       return ec_node_complete_child(node->cmd, comp, strvec);
+}
+
+static void ec_node_cmd_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+       size_t i;
+
+       ec_free(node->cmd_str);
+       node->cmd_str = NULL;
+       ec_node_free(node->expr);
+       node->expr = NULL;
+       ec_node_free(node->parser);
+       node->parser = NULL;
+       ec_node_free(node->cmd);
+       node->cmd = NULL;
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = NULL;
+       node->len = 0;
+}
+
+static const struct ec_config_schema ec_node_cmd_subschema[] = {
+       {
+               .desc = "A child node whose id is referenced in the expression.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema ec_node_cmd_schema[] = {
+       {
+               .key = "expr",
+               .desc = "The expression to match. Supported operators "
+               "are or '|', list ',', many '+', many-or-zero '*', "
+               "option '[]', group '()'. An identifier (alphanumeric) can "
+               "reference a node whose node_id matches. Else it is "
+               "interpreted as ec_node_str() matching this string. "
+               "Example: command [option] (subset1, subset2) x|y",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .key = "children",
+               .desc = "The list of children nodes.",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = ec_node_cmd_subschema,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_cmd_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+       const struct ec_config *expr = NULL;
+       struct ec_node *cmd = NULL;
+       struct ec_node **table = NULL;
+       char *cmd_str = NULL;
+       size_t len = 0, i;
+
+       /* retrieve config locally */
+       expr = ec_config_dict_get(config, "expr");
+       if (expr == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       table = ec_node_config_node_list_to_table(
+               ec_config_dict_get(config, "children"), &len);
+       if (table == NULL)
+               goto fail;
+
+       cmd_str = ec_strdup(expr->string);
+       if (cmd_str == NULL)
+               goto fail;
+
+       /* parse expression to build the cmd child node */
+       cmd = ec_node_cmd_build(node, cmd_str, table, len);
+       if (cmd == NULL)
+               goto fail;
+
+       /* ok, store the config */
+       ec_node_free(node->cmd);
+       node->cmd = cmd;
+       ec_free(node->cmd_str);
+       node->cmd_str = cmd_str;
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = table;
+       node->len = len;
+
+       return 0;
+
+fail:
+       for (i = 0; i < len; i++)
+               ec_node_free(table[i]);
+       ec_free(table);
+       ec_free(cmd_str);
+       ec_node_free(cmd);
+       return -1;
+}
+
+static size_t
+ec_node_cmd_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+
+       if (node->cmd == NULL)
+               return 0;
+       return 1;
+}
+
+static int
+ec_node_cmd_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+
+       if (i > 0)
+               return -1;
+
+       *child = node->cmd;
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_cmd_type = {
+       .name = "cmd",
+       .schema = ec_node_cmd_schema,
+       .set_config = ec_node_cmd_set_config,
+       .parse = ec_node_cmd_parse,
+       .complete = ec_node_cmd_complete,
+       .size = sizeof(struct ec_node_cmd),
+       .free_priv = ec_node_cmd_free_priv,
+       .get_children_count = ec_node_cmd_get_children_count,
+       .get_child = ec_node_cmd_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_cmd_type);
+
+struct ec_node *__ec_node_cmd(const char *id, const char *cmd, ...)
+{
+       struct ec_config *config = NULL, *children = NULL;
+       struct ec_node *gen_node = NULL;
+       struct ec_node_cmd *node = NULL;
+       va_list ap;
+       int ret;
+
+       /* this block must stay first, it frees the nodes on error */
+       va_start(ap, cmd);
+       children = ec_node_config_node_list_from_vargs(ap);
+       va_end(ap);
+       if (children == NULL)
+               goto fail;
+
+       gen_node = ec_node_from_type(&ec_node_cmd_type, id);
+       if (gen_node == NULL)
+               goto fail;
+       node = (struct ec_node_cmd *)gen_node;
+
+       node->expr = ec_node_cmd_build_expr();
+       if (node->expr == NULL)
+               goto fail;
+
+       node->parser = ec_node_cmd_build_parser(node->expr);
+       if (node->parser == 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, "children", children) < 0) {
+               children = NULL; /* freed */
+               goto fail;
+       }
+       children = 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); /* will also free added children */
+       ec_config_free(children);
+       ec_config_free(config);
+
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_cmd_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = EC_NODE_CMD(EC_NO_ID,
+               "command [option] (subset1, subset2, subset3, subset4) x|y z*",
+               ec_node_int("x", 0, 10, 10),
+               ec_node_int("y", 20, 30, 10)
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "subset1", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 4, "command", "subset3", "subset2",
+                               "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "subset2", "subset3",
+                               "subset1", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 6, "command", "subset3", "subset1",
+                               "subset4", "subset2", "4");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "23");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "option", "23");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "option", "23",
+                               "z", "z");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "command", "15");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       ec_node_free(node);
+
+       node = EC_NODE_CMD(EC_NO_ID, "good morning [count] bob|bobby|michael",
+                       ec_node_int("count", 0, 10, 10));
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 4, "good", "morning", "1", "bob");
+
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "good", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "g", EC_NODE_ENDLIST,
+               "good", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "good", "morning", "", EC_NODE_ENDLIST,
+               "bob", "bobby", "michael", EC_NODE_ENDLIST);
+
+       ec_node_free(node);
+
+       node = EC_NODE_CMD(EC_NO_ID, "[foo [bar]]");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "x");
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_cmd_test = {
+       .name = "node_cmd",
+       .test = ec_node_cmd_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_cmd_test);
diff --git a/libecoli/ecoli_node_cmd.h b/libecoli/ecoli_node_cmd.h
new file mode 100644 (file)
index 0000000..99afc01
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_CMD_
+#define ECOLI_NODE_CMD_
+
+#include <ecoli_node.h>
+
+#define EC_NODE_CMD(args...) __ec_node_cmd(args, EC_NODE_ENDLIST)
+
+struct ec_node *__ec_node_cmd(const char *id, const char *cmd_str, ...);
+
+#endif
diff --git a/libecoli/ecoli_node_dynamic.c b/libecoli/ecoli_node_dynamic.c
new file mode 100644 (file)
index 0000000..8a3edf3
--- /dev/null
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2017, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_string.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_many.h>
+
+#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_parse *parse,
+               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 = -1;
+
+       child = node->build(parse, node->opaque);
+       if (child == NULL)
+               goto fail;
+
+       /* add the node pointer in the attributes, so it will be freed
+        * when parse is freed */
+       snprintf(key, sizeof(key), "_dyn_%p", child);
+       ret = ec_keyval_set(ec_parse_get_attrs(parse), key, child,
+                       (void *)node_free);
+       if (ret < 0) {
+               child = NULL; /* already freed */
+               goto fail;
+       }
+
+       return ec_node_parse_child(child, parse, strvec);
+
+fail:
+       ec_node_free(child);
+       return ret;
+}
+
+static int
+ec_node_dynamic_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node;
+       struct ec_parse *parse;
+       struct ec_node *child = NULL;
+       void (*node_free)(struct ec_node *) = ec_node_free;
+       char key[64];
+       int ret = -1;
+
+       parse = ec_comp_get_state(comp);
+       child = node->build(parse, node->opaque);
+       if (child == NULL)
+               goto fail;
+
+       /* add the node pointer in the attributes, so it will be freed
+        * when parse is freed */
+       snprintf(key, sizeof(key), "_dyn_%p", child);
+       ret = ec_keyval_set(comp->attrs, key, child,
+                       (void *)node_free);
+       if (ret < 0) {
+               child = NULL; /* already freed */
+               goto fail;
+       }
+
+       return ec_node_complete_child(child, comp, 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_from_type(&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_parse *parse, void *opaque)
+{
+       const struct ec_node *node;
+       struct ec_parse *iter;
+       unsigned int count = 0;
+       char buf[32];
+
+       (void)opaque;
+       for (iter = ec_parse_get_root(parse); iter != NULL;
+            iter = ec_parse_iter_next(iter)) {
+               node = ec_parse_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 testres = 0;
+
+       node = ec_node_many(EC_NO_ID,
+                       ec_node_dynamic(EC_NO_ID, build_counter, NULL),
+                       1, 3);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "count-0", "count-1", "count-2");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0", "count-0");
+
+       /* test completion */
+
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "c", EC_NODE_ENDLIST,
+               "count-0", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "count-0", "", EC_NODE_ENDLIST,
+               "count-1", EC_NODE_ENDLIST,
+               "count-1");
+       ec_node_free(node);
+
+       return testres;
+}
+/* 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/libecoli/ecoli_node_dynamic.h b/libecoli/ecoli_node_dynamic.h
new file mode 100644 (file)
index 0000000..4f2535e
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2017, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_DYNAMIC_
+#define ECOLI_NODE_DYNAMIC_
+
+struct ec_node;
+struct ec_parse;
+
+/* 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_parse *state, void *opaque);
+
+struct ec_node *ec_node_dynamic(const char *id, ec_node_dynamic_build_t build,
+                               void *opaque);
+
+#endif
diff --git a/libecoli/ecoli_node_empty.c b/libecoli/ecoli_node_empty.c
new file mode 100644 (file)
index 0000000..6ce76e8
--- /dev/null
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_empty.h>
+
+EC_LOG_TYPE_REGISTER(node_empty);
+
+struct ec_node_empty {
+       struct ec_node gen;
+};
+
+static int ec_node_empty_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)state;
+       (void)strvec;
+       return 0;
+}
+
+static struct ec_node_type ec_node_empty_type = {
+       .name = "empty",
+       .parse = ec_node_empty_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_empty),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_empty_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_empty_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("empty", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "foo", "bar");
+       ec_node_free(node);
+
+       /* never completes */
+       node = ec_node("empty", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_empty_test = {
+       .name = "node_empty",
+       .test = ec_node_empty_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_empty_test);
diff --git a/libecoli/ecoli_node_empty.h b/libecoli/ecoli_node_empty.h
new file mode 100644 (file)
index 0000000..ed5e32e
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * This node always matches an empty string vector
+ */
+
+#ifndef ECOLI_NODE_EMPTY_
+#define ECOLI_NODE_EMPTY_
+
+struct ec_node *ec_node_empty(const char *id);
+
+#endif
diff --git a/libecoli/ecoli_node_expr.c b/libecoli/ecoli_node_expr.c
new file mode 100644 (file)
index 0000000..3b47d8c
--- /dev/null
@@ -0,0 +1,617 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_expr.h>
+
+EC_LOG_TYPE_REGISTER(node_expr);
+
+struct ec_node_expr {
+       struct ec_node gen;
+
+       /* the built node */
+       struct ec_node *child;
+
+       /* the configuration nodes */
+       struct ec_node *val_node;
+       struct ec_node **bin_ops;
+       unsigned int bin_ops_len;
+       struct ec_node **pre_ops;
+       unsigned int pre_ops_len;
+       struct ec_node **post_ops;
+       unsigned int post_ops_len;
+       struct ec_node **open_ops;
+       struct ec_node **close_ops;
+       unsigned int paren_len;
+};
+
+static int ec_node_expr_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (node->child == NULL) {
+               errno = ENOENT;
+               return -1;
+       }
+
+       return ec_node_parse_child(node->child, state, strvec);
+}
+
+static int
+ec_node_expr_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (node->child == NULL) {
+               errno = ENOENT;
+               return -1;
+       }
+
+       return ec_node_complete_child(node->child, comp, strvec);
+}
+
+static void ec_node_expr_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       unsigned int i;
+
+       ec_node_free(node->child);
+       ec_node_free(node->val_node);
+
+       for (i = 0; i < node->bin_ops_len; i++)
+               ec_node_free(node->bin_ops[i]);
+       ec_free(node->bin_ops);
+       for (i = 0; i < node->pre_ops_len; i++)
+               ec_node_free(node->pre_ops[i]);
+       ec_free(node->pre_ops);
+       for (i = 0; i < node->post_ops_len; i++)
+               ec_node_free(node->post_ops[i]);
+       ec_free(node->post_ops);
+       for (i = 0; i < node->paren_len; i++) {
+               ec_node_free(node->open_ops[i]);
+               ec_node_free(node->close_ops[i]);
+       }
+       ec_free(node->open_ops);
+       ec_free(node->close_ops);
+}
+
+static int ec_node_expr_build(struct ec_node_expr *node)
+{
+       struct ec_node *term = NULL, *expr = NULL, *next = NULL,
+               *pre_op = NULL, *post_op = NULL, *ref = NULL,
+               *post = NULL;
+       unsigned int i;
+
+       ec_node_free(node->child);
+       node->child = NULL;
+
+       if (node->val_node == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (node->bin_ops_len == 0 && node->pre_ops_len == 0 &&
+                       node->post_ops_len == 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       /*
+        * Example of created grammar:
+        *
+        * pre_op = "!"
+        * post_op = "^"
+        * post = val |
+        *        pre_op expr |
+        *        "(" expr ")"
+        * term = post post_op*
+        * prod = term ( "*" term )*
+        * sum = prod ( "+" prod )*
+        * expr = sum
+        */
+
+       /* we use this as a ref, will be set later */
+       ref = ec_node("seq", "ref");
+       if (ref == NULL)
+               return -1;
+
+       /* prefix unary operators */
+       pre_op = ec_node("or", "pre-op");
+       if (pre_op == NULL)
+               goto fail;
+       for (i = 0; i < node->pre_ops_len; i++) {
+               if (ec_node_or_add(pre_op, ec_node_clone(node->pre_ops[i])) < 0)
+                       goto fail;
+       }
+
+       /* suffix unary operators */
+       post_op = ec_node("or", "post-op");
+       if (post_op == NULL)
+               goto fail;
+       for (i = 0; i < node->post_ops_len; i++) {
+               if (ec_node_or_add(post_op, ec_node_clone(node->post_ops[i])) < 0)
+                       goto fail;
+       }
+
+       post = ec_node("or", "post");
+       if (post == NULL)
+               goto fail;
+       if (ec_node_or_add(post, ec_node_clone(node->val_node)) < 0)
+               goto fail;
+       if (ec_node_or_add(post,
+               EC_NODE_SEQ(EC_NO_ID,
+                       ec_node_clone(pre_op),
+                       ec_node_clone(ref))) < 0)
+               goto fail;
+       for (i = 0; i < node->paren_len; i++) {
+               if (ec_node_or_add(post, EC_NODE_SEQ(EC_NO_ID,
+                                       ec_node_clone(node->open_ops[i]),
+                                       ec_node_clone(ref),
+                                       ec_node_clone(node->close_ops[i]))) < 0)
+                       goto fail;
+       }
+       term = EC_NODE_SEQ("term",
+               ec_node_clone(post),
+               ec_node_many(EC_NO_ID, ec_node_clone(post_op), 0, 0)
+       );
+       if (term == NULL)
+               goto fail;
+
+       for (i = 0; i < node->bin_ops_len; i++) {
+               next = EC_NODE_SEQ("next",
+                       ec_node_clone(term),
+                       ec_node_many(EC_NO_ID,
+                               EC_NODE_SEQ(EC_NO_ID,
+                                       ec_node_clone(node->bin_ops[i]),
+                                       ec_node_clone(term)
+                               ),
+                               0, 0
+                       )
+               );
+               ec_node_free(term);
+               term = next;
+               if (term == NULL)
+                       goto fail;
+       }
+       expr = term;
+       term = NULL;
+
+       /* free the initial references */
+       ec_node_free(pre_op);
+       pre_op = NULL;
+       ec_node_free(post_op);
+       post_op = NULL;
+       ec_node_free(post);
+       post = NULL;
+
+       if (ec_node_seq_add(ref, ec_node_clone(expr)) < 0)
+               goto fail;
+       ec_node_free(ref);
+       ref = NULL;
+
+       node->child = expr;
+
+       return 0;
+
+fail:
+       ec_node_free(term);
+       ec_node_free(expr);
+       ec_node_free(pre_op);
+       ec_node_free(post_op);
+       ec_node_free(post);
+       ec_node_free(ref);
+
+       return -1;
+}
+
+static size_t
+ec_node_expr_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_expr_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_expr_type = {
+       .name = "expr",
+       .parse = ec_node_expr_parse,
+       .complete = ec_node_expr_complete,
+       .size = sizeof(struct ec_node_expr),
+       .free_priv = ec_node_expr_free_priv,
+       .get_children_count = ec_node_expr_get_children_count,
+       .get_child = ec_node_expr_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_expr_type);
+
+int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (val_node == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       ec_node_free(node->val_node);
+       node->val_node = val_node;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(val_node);
+       return -1;
+}
+
+/* add a binary operator */
+int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       struct ec_node **bin_ops;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (node == NULL || op == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       bin_ops = ec_realloc(node->bin_ops,
+               (node->bin_ops_len + 1) * sizeof(*node->bin_ops));
+       if (bin_ops == NULL)
+               goto fail;;
+
+       node->bin_ops = bin_ops;
+       bin_ops[node->bin_ops_len] = op;
+       node->bin_ops_len++;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(op);
+       return -1;
+}
+
+/* add a unary pre-operator */
+int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       struct ec_node **pre_ops;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (node == NULL || op == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       pre_ops = ec_realloc(node->pre_ops,
+               (node->pre_ops_len + 1) * sizeof(*node->pre_ops));
+       if (pre_ops == NULL)
+               goto fail;
+
+       node->pre_ops = pre_ops;
+       pre_ops[node->pre_ops_len] = op;
+       node->pre_ops_len++;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(op);
+       return -1;
+}
+
+/* add a unary post-operator */
+int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       struct ec_node **post_ops;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (node == NULL || op == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       post_ops = ec_realloc(node->post_ops,
+               (node->post_ops_len + 1) * sizeof(*node->post_ops));
+       if (post_ops == NULL)
+               goto fail;
+
+       node->post_ops = post_ops;
+       post_ops[node->post_ops_len] = op;
+       node->post_ops_len++;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(op);
+       return -1;
+}
+
+/* add parenthesis symbols */
+int ec_node_expr_add_parenthesis(struct ec_node *gen_node,
+       struct ec_node *open, struct ec_node *close)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       struct ec_node **open_ops, **close_ops;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (node == NULL || open == NULL || close == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       open_ops = ec_realloc(node->open_ops,
+               (node->paren_len + 1) * sizeof(*node->open_ops));
+       if (open_ops == NULL)
+               goto fail;
+       close_ops = ec_realloc(node->close_ops,
+               (node->paren_len + 1) * sizeof(*node->close_ops));
+       if (close_ops == NULL)
+               goto fail;
+
+       node->open_ops = open_ops;
+       node->close_ops = close_ops;
+       open_ops[node->paren_len] = open;
+       close_ops[node->paren_len] = close;
+       node->paren_len++;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(open);
+       ec_node_free(close);
+       return -1;
+}
+
+enum expr_node_type {
+       NONE,
+       VAL,
+       BIN_OP,
+       PRE_OP,
+       POST_OP,
+       PAREN_OPEN,
+       PAREN_CLOSE,
+};
+
+static enum expr_node_type get_node_type(const struct ec_node *expr_gen_node,
+       const struct ec_node *check)
+{
+       struct ec_node_expr *expr_node = (struct ec_node_expr *)expr_gen_node;
+       size_t i;
+
+       if (check == expr_node->val_node)
+               return VAL;
+
+       for (i = 0; i < expr_node->bin_ops_len; i++) {
+               if (check == expr_node->bin_ops[i])
+                       return BIN_OP;
+       }
+       for (i = 0; i < expr_node->pre_ops_len; i++) {
+               if (check == expr_node->pre_ops[i])
+                       return PRE_OP;
+       }
+       for (i = 0; i < expr_node->post_ops_len; i++) {
+               if (check == expr_node->post_ops[i])
+                       return POST_OP;
+       }
+
+       for (i = 0; i < expr_node->paren_len; i++) {
+               if (check == expr_node->open_ops[i])
+                       return PAREN_OPEN;
+       }
+       for (i = 0; i < expr_node->paren_len; i++) {
+               if (check == expr_node->close_ops[i])
+                       return PAREN_CLOSE;
+       }
+
+       return NONE;
+}
+
+struct result {
+       bool has_val;
+       void *val;
+       const struct ec_parse *op;
+       enum expr_node_type op_type;
+};
+
+/* merge x and y results in x */
+static int merge_results(void *userctx,
+       const struct ec_node_expr_eval_ops *ops,
+       struct result *x, const struct result *y)
+{
+       if (y->has_val == 0 && y->op == NULL)
+               return 0;
+       if (x->has_val == 0 && x->op == NULL) {
+               *x = *y;
+               return 0;
+       }
+
+       if (x->has_val && y->has_val && y->op != NULL) {
+               if (y->op_type == BIN_OP) {
+                       if (ops->eval_bin_op(&x->val, userctx, x->val,
+                                       y->op, y->val) < 0)
+                               return -1;
+
+                       return 0;
+               }
+       }
+
+       if (x->has_val == 0 && x->op != NULL && y->has_val && y->op == NULL) {
+               if (x->op_type == PRE_OP) {
+                       if (ops->eval_pre_op(&x->val, userctx, y->val,
+                                               x->op) < 0)
+                               return -1;
+                       x->has_val = true;
+                       x->op_type = NONE;
+                       x->op = NULL;
+                       return 0;
+               } else if (x->op_type == BIN_OP) {
+                       x->val = y->val;
+                       x->has_val = true;
+                       return 0;
+               }
+       }
+
+       if (x->has_val && x->op == NULL && y->has_val == 0 && y->op != NULL) {
+               if (ops->eval_post_op(&x->val, userctx, x->val, y->op) < 0)
+                       return -1;
+
+               return 0;
+       }
+
+       assert(false); /* we should not get here */
+       return -1;
+}
+
+static int eval_expression(struct result *result,
+       void *userctx,
+       const struct ec_node_expr_eval_ops *ops,
+       const struct ec_node *expr_gen_node,
+       const struct ec_parse *parse)
+
+{
+       struct ec_parse *open = NULL, *close = NULL;
+       struct result child_result;
+       struct ec_parse *child;
+       enum expr_node_type type;
+
+       memset(result, 0, sizeof(*result));
+       memset(&child_result, 0, sizeof(child_result));
+
+       type = get_node_type(expr_gen_node, ec_parse_get_node(parse));
+       if (type == VAL) {
+               if (ops->eval_var(&result->val, userctx, parse) < 0)
+                       goto fail;
+               result->has_val = 1;
+       } else if (type == PRE_OP || type == POST_OP || type == BIN_OP) {
+               result->op = parse;
+               result->op_type = type;
+       }
+
+       EC_PARSE_FOREACH_CHILD(child, parse) {
+
+               type = get_node_type(expr_gen_node, ec_parse_get_node(child));
+               if (type == PAREN_OPEN) {
+                       open = child;
+                       continue;
+               } else if (type == PAREN_CLOSE) {
+                       close = child;
+                       continue;
+               }
+
+               if (eval_expression(&child_result, userctx, ops,
+                       expr_gen_node, child) < 0)
+                       goto fail;
+
+               if (merge_results(userctx, ops, result, &child_result) < 0)
+                       goto fail;
+
+               memset(&child_result, 0, sizeof(child_result));
+       }
+
+       if (open != NULL && close != NULL) {
+               if (ops->eval_parenthesis(&result->val, userctx, open, close,
+                       result->val) < 0)
+                       goto fail;
+       }
+
+       return 0;
+
+fail:
+       if (result->has_val)
+               ops->eval_free(result->val, userctx);
+       if (child_result.has_val)
+               ops->eval_free(child_result.val, userctx);
+       memset(result, 0, sizeof(*result));
+
+       return -1;
+}
+
+int ec_node_expr_eval(void **user_result, const struct ec_node *node,
+       struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops,
+       void *userctx)
+{
+       struct result result;
+
+       if (ops == NULL || ops->eval_var == NULL || ops->eval_pre_op == NULL ||
+                       ops->eval_post_op == NULL || ops->eval_bin_op == NULL ||
+                       ops->eval_parenthesis == NULL ||
+                       ops->eval_free == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (ec_node_check_type(node, &ec_node_expr_type) < 0)
+               return -1;
+
+       if (!ec_parse_matches(parse)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (eval_expression(&result, userctx, ops, node, parse) < 0)
+               return -1;
+
+       assert(result.has_val);
+       assert(result.op == NULL);
+       *user_result = result.val;
+
+       return 0;
+}
+
+/* the test case is in a separate file ecoli_node_expr_test.c */
diff --git a/libecoli/ecoli_node_expr.h b/libecoli/ecoli_node_expr.h
new file mode 100644 (file)
index 0000000..4f21d81
--- /dev/null
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_EXPR_
+#define ECOLI_NODE_EXPR_
+
+#include <ecoli_node.h>
+
+/**
+ * Callback function type for evaluating a variable
+ *
+ * @param result
+ *   On success, this pointer must be set by the user to point
+ *   to a user structure describing the evaluated result.
+ * @param userctx
+ *   A user-defined context passed to all callback functions, which
+ *   can be used to maintain a state or store global information.
+ * @param var
+ *   The parse result referencing the variable.
+ * @return
+ *   0 on success (*result must be set), or -errno on error (*result
+ *   is undefined).
+ */
+typedef int (*ec_node_expr_eval_var_t)(
+       void **result, void *userctx,
+       const struct ec_parse *var);
+
+/**
+ * Callback function type for evaluating a prefix-operator
+ *
+ * @param result
+ *   On success, this pointer must be set by the user to point
+ *   to a user structure describing the evaluated result.
+ * @param userctx
+ *   A user-defined context passed to all callback functions, which
+ *   can be used to maintain a state or store global information.
+ * @param operand
+ *   The evaluated expression on which the operation should be applied.
+ * @param var
+ *   The parse result referencing the operator.
+ * @return
+ *   0 on success (*result must be set, operand is freed),
+ *   or -errno on error (*result is undefined, operand is not freed).
+ */
+typedef int (*ec_node_expr_eval_pre_op_t)(
+       void **result, void *userctx,
+       void *operand,
+       const struct ec_parse *operator);
+
+typedef int (*ec_node_expr_eval_post_op_t)(
+       void **result, void *userctx,
+       void *operand,
+       const struct ec_parse *operator);
+
+typedef int (*ec_node_expr_eval_bin_op_t)(
+       void **result, void *userctx,
+       void *operand1,
+       const struct ec_parse *operator,
+       void *operand2);
+
+typedef int (*ec_node_expr_eval_parenthesis_t)(
+       void **result, void *userctx,
+       const struct ec_parse *open_paren,
+       const struct ec_parse *close_paren,
+       void * value);
+
+typedef void (*ec_node_expr_eval_free_t)(
+       void *result, void *userctx);
+
+
+struct ec_node *ec_node_expr(const char *id);
+int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node);
+int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op);
+int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op);
+int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op);
+int ec_node_expr_add_parenthesis(struct ec_node *gen_node,
+       struct ec_node *open, struct ec_node *close);
+
+struct ec_node_expr_eval_ops {
+       ec_node_expr_eval_var_t eval_var;
+       ec_node_expr_eval_pre_op_t eval_pre_op;
+       ec_node_expr_eval_post_op_t eval_post_op;
+       ec_node_expr_eval_bin_op_t eval_bin_op;
+       ec_node_expr_eval_parenthesis_t eval_parenthesis;
+       ec_node_expr_eval_free_t eval_free;
+};
+
+int ec_node_expr_eval(void **result, const struct ec_node *node,
+       struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops,
+       void *userctx);
+
+#endif
diff --git a/libecoli/ecoli_node_expr_test.c b/libecoli/ecoli_node_expr_test.c
new file mode 100644 (file)
index 0000000..93e33a4
--- /dev/null
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <assert.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_node_int.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_re_lex.h>
+#include <ecoli_node_expr.h>
+
+EC_LOG_TYPE_REGISTER(node_expr);
+
+struct my_eval_result {
+       int val;
+};
+
+static int
+ec_node_expr_test_eval_var(void **result, void *userctx,
+       const struct ec_parse *var)
+{
+       const struct ec_strvec *vec;
+       const struct ec_node *node;
+       struct my_eval_result *eval = NULL;
+       int64_t val;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(var);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       node = ec_parse_get_node(var);
+       if (ec_node_int_getval(node, ec_strvec_val(vec, 0), &val) < 0)
+               return -1;
+
+       eval = ec_malloc(sizeof(*eval));
+       if (eval == NULL)
+               return -1;
+
+       eval->val = val;
+       EC_LOG(EC_LOG_DEBUG, "eval var %d\n", eval->val);
+       *result = eval;
+
+       return 0;
+}
+
+static int
+ec_node_expr_test_eval_pre_op(void **result, void *userctx, void *operand,
+       const struct ec_parse *operator)
+{
+       const struct ec_strvec *vec;
+       struct my_eval_result *eval = operand;;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "!")) {
+               eval->val = !eval->val;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+
+       EC_LOG(EC_LOG_DEBUG, "eval pre_op %d\n", eval->val);
+       *result = eval;
+
+       return 0;
+}
+
+static int
+ec_node_expr_test_eval_post_op(void **result, void *userctx, void *operand,
+       const struct ec_parse *operator)
+{
+       const struct ec_strvec *vec;
+       struct my_eval_result *eval = operand;;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "^")) {
+               eval->val = eval->val * eval->val;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       EC_LOG(EC_LOG_DEBUG, "eval post_op %d\n", eval->val);
+       *result = eval;
+
+       return 0;
+}
+
+static int
+ec_node_expr_test_eval_bin_op(void **result, void *userctx, void *operand1,
+       const struct ec_parse *operator, void *operand2)
+
+{
+       const struct ec_strvec *vec;
+       struct my_eval_result *eval1 = operand1;;
+       struct my_eval_result *eval2 = operand2;;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "+")) {
+               eval1->val = eval1->val + eval2->val;
+       } else if (!strcmp(ec_strvec_val(vec, 0), "*")) {
+               eval1->val = eval1->val * eval2->val;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       EC_LOG(EC_LOG_DEBUG, "eval bin_op %d\n", eval1->val);
+       ec_free(eval2);
+       *result = eval1;
+
+       return 0;
+}
+
+static int
+ec_node_expr_test_eval_parenthesis(void **result, void *userctx,
+       const struct ec_parse *open_paren,
+       const struct ec_parse *close_paren,
+       void *value)
+{
+       (void)userctx;
+       (void)open_paren;
+       (void)close_paren;
+
+       EC_LOG(EC_LOG_DEBUG, "eval paren\n");
+       *result = value;
+
+       return 0;
+}
+
+static void
+ec_node_expr_test_eval_free(void *result, void *userctx)
+{
+       (void)userctx;
+       ec_free(result);
+}
+
+static const struct ec_node_expr_eval_ops test_ops = {
+       .eval_var = ec_node_expr_test_eval_var,
+       .eval_pre_op = ec_node_expr_test_eval_pre_op,
+       .eval_post_op = ec_node_expr_test_eval_post_op,
+       .eval_bin_op = ec_node_expr_test_eval_bin_op,
+       .eval_parenthesis = ec_node_expr_test_eval_parenthesis,
+       .eval_free = ec_node_expr_test_eval_free,
+};
+
+static int ec_node_expr_test_eval(struct ec_node *lex_node,
+       const struct ec_node *expr_node,
+       const char *str, int val)
+{
+       struct ec_parse *p;
+       void *result;
+       struct my_eval_result *eval;
+       int ret;
+
+       p = ec_node_parse(lex_node, str);
+       if (p == NULL)
+               return -1;
+
+       ret = ec_node_expr_eval(&result, expr_node, p, &test_ops, NULL);
+       ec_parse_free(p);
+       if (ret < 0)
+               return -1;
+
+       /* the parse value is an integer */
+       eval = result;
+       assert(eval != NULL);
+
+       EC_LOG(EC_LOG_DEBUG, "result: %d (expected %d)\n", eval->val, val);
+       if (eval->val == val)
+               ret = 0;
+       else
+               ret = -1;
+
+       ec_free(eval);
+
+       return ret;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_expr_testcase(void)
+{
+       struct ec_node *node = NULL, *lex_node = NULL;
+       int testres = 0;
+
+       node = ec_node("expr", "my_expr");
+       if (node == NULL)
+               return -1;
+
+       ec_node_expr_set_val_node(node, ec_node_int(EC_NO_ID, 0, UCHAR_MAX, 0));
+       ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "+"));
+       ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "*"));
+       ec_node_expr_add_pre_op(node, ec_node_str(EC_NO_ID, "!"));  /* not */
+       ec_node_expr_add_post_op(node, ec_node_str(EC_NO_ID, "^")); /* square */
+       ec_node_expr_add_parenthesis(node, ec_node_str(EC_NO_ID, "("),
+               ec_node_str(EC_NO_ID, ")"));
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "*");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1", "*");
+       testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "+", "!", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "^", "+", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "*", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "+", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 7, "1", "*", "1", "*", "1", "*",
+                               "1");
+       testres |= EC_TEST_CHECK_PARSE(
+               node, 10, "!", "(", "1", "*", "(", "1", "+", "1", ")", ")");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "+", "!", "1", "^");
+
+       /* prepend a lexer to the expression node */
+       lex_node = ec_node_re_lex(EC_NO_ID, ec_node_clone(node));
+       if (lex_node == NULL)
+               goto fail;
+
+       testres |= ec_node_re_lex_add(lex_node, "[0-9]+", 1); /* vars */
+       testres |= ec_node_re_lex_add(lex_node, "[+*!^()]", 1); /* operators */
+       testres |= ec_node_re_lex_add(lex_node, "[      ]+", 0); /* spaces */
+
+       /* valid expressions */
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^ + 1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1 + 4 * (2 + 3^)^");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1)");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "3*!3+!3*(2+ 2)");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!!(!1)^ + !(4 + (2*3))");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1 + 1)^ * 1^");
+
+       /* invalid expressions */
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "()");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "(");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, ")");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "+1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+*1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+(1*1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+!1!1)");
+
+       testres |= ec_node_expr_test_eval(lex_node, node, "1^", 1);
+       testres |= ec_node_expr_test_eval(lex_node, node, "2^", 4);
+       testres |= ec_node_expr_test_eval(lex_node, node, "!1", 0);
+       testres |= ec_node_expr_test_eval(lex_node, node, "!0", 1);
+
+       testres |= ec_node_expr_test_eval(lex_node, node, "1+1", 2);
+       testres |= ec_node_expr_test_eval(lex_node, node, "1+2+3", 6);
+       testres |= ec_node_expr_test_eval(lex_node, node, "1+1*2", 4);
+       testres |= ec_node_expr_test_eval(lex_node, node, "2 * 2^", 8);
+       testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !0)^ * !0^", 4);
+       testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !1) * 3", 3);
+
+       ec_node_free(node);
+       ec_node_free(lex_node);
+
+       return testres;
+
+fail:
+       ec_node_free(lex_node);
+       ec_node_free(node);
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_expr_test = {
+       .name = "node_expr",
+       .test = ec_node_expr_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_expr_test);
diff --git a/libecoli/ecoli_node_file.c b/libecoli/ecoli_node_file.c
new file mode 100644 (file)
index 0000000..001dcb6
--- /dev/null
@@ -0,0 +1,425 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_string.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_file.h>
+
+EC_LOG_TYPE_REGISTER(node_file);
+
+struct ec_node_file {
+       struct ec_node gen;
+
+       /* below functions pointers are only useful for test */
+       int (*lstat)(const char *pathname, struct stat *buf);
+       DIR *(*opendir)(const char *name);
+       struct dirent *(*readdir)(DIR *dirp);
+       int (*closedir)(DIR *dirp);
+       int (*dirfd)(DIR *dirp);
+       int (*fstatat)(int dirfd, const char *pathname, struct stat *buf,
+               int flags);
+};
+
+static int
+ec_node_file_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+/*
+ * Almost the same than dirname (3) and basename (3) except that:
+ * - it always returns a substring of the given path, which can
+ *   be empty.
+ * - the behavior is different when the path finishes with a '/'
+ * - the path argument is not modified
+ * - the outputs are allocated and must be freed with ec_free().
+ *
+ *   path       dirname   basename       split_path
+ *   /usr/lib   /usr      lib          /usr/     lib
+ *   /usr/      /         usr          /usr/
+ *   usr        .         usr                    usr
+ *   /          /         /            /
+ *   .          .         .                      .
+ *   ..         .         ..                     ..
+ */
+static int split_path(const char *path, char **dname_p, char **bname_p)
+{
+       char *last_slash;
+       size_t dirlen;
+       char *dname, *bname;
+
+       *dname_p = NULL;
+       *bname_p = NULL;
+
+       last_slash = strrchr(path, '/');
+       if (last_slash == NULL)
+               dirlen = 0;
+       else
+               dirlen = last_slash - path + 1;
+
+       dname = ec_strdup(path);
+       if (dname == NULL)
+               return -1;
+       dname[dirlen] = '\0';
+
+       bname = ec_strdup(path + dirlen);
+       if (bname == NULL) {
+               ec_free(dname);
+               return -1;
+       }
+
+       *dname_p = dname;
+       *bname_p = bname;
+
+       return 0;
+}
+
+static int
+ec_node_file_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_file *node = (struct ec_node_file *)gen_node;
+       char *dname = NULL, *bname = NULL, *effective_dir;
+       struct ec_comp_item *item = NULL;
+       enum ec_comp_type type;
+       struct stat st, st2;
+       const char *input;
+       size_t bname_len;
+       struct dirent *de = NULL;
+       DIR *dir = NULL;
+       char *comp_str = NULL;
+       char *disp_str = NULL;
+       int is_dir = 0;
+
+       /*
+        * Example with this file tree:
+        * /
+        * ├── dir1
+        * │   ├── file1
+        * │   ├── file2
+        * │   └── subdir
+        * │       └── file3
+        * ├── dir2
+        * │   └── file4
+        * └── file5
+        *
+        * Input     Output completions
+        *   /       [dir1/, dir2/, file5]
+        *   /d      [dir1/, dir2/]
+        *   /f      [file5]
+        *   /dir1/  [file1, file2, subdir/]
+        *
+        *
+        *
+        */
+
+       if (ec_strvec_len(strvec) != 1)
+               return 0;
+
+       input = ec_strvec_val(strvec, 0);
+       if (split_path(input, &dname, &bname) < 0)
+               return -1;
+
+       if (strcmp(dname, "") == 0)
+               effective_dir = ".";
+       else
+               effective_dir = dname;
+
+       if (node->lstat(effective_dir, &st) < 0)
+               goto fail;
+       if (!S_ISDIR(st.st_mode))
+               goto out;
+
+       dir = node->opendir(effective_dir);
+       if (dir == NULL)
+               goto fail;
+
+       bname_len = strlen(bname);
+       while (1) {
+               int save_errno = errno;
+
+               errno = 0;
+               de = node->readdir(dir);
+               if (de == NULL) {
+                       if (errno == 0) {
+                               errno = save_errno;
+                               goto out;
+                       } else {
+                               goto fail;
+                       }
+               }
+
+               if (!ec_str_startswith(de->d_name, bname))
+                       continue;
+               if (bname[0] != '.' && de->d_name[0] == '.')
+                       continue;
+
+               /* add '/' if it's a dir */
+               if (de->d_type == DT_DIR) {
+                       is_dir = 1;
+               } else if (de->d_type == DT_UNKNOWN) {
+                       int dir_fd = node->dirfd(dir);
+
+                       if (dir_fd < 0)
+                               goto fail;
+                       if (node->fstatat(dir_fd, de->d_name, &st2, 0) < 0)
+                               goto fail;
+                       if (S_ISDIR(st2.st_mode))
+                               is_dir = 1;
+                       else
+                               is_dir = 0;
+               } else {
+                       is_dir = 0;
+               }
+
+               if (is_dir) {
+                       type = EC_COMP_PARTIAL;
+                       if (ec_asprintf(&comp_str, "%s%s/", input,
+                                       &de->d_name[bname_len]) < 0)
+                               goto fail;
+                       if (ec_asprintf(&disp_str, "%s/", de->d_name) < 0)
+                               goto fail;
+               } else {
+                       type = EC_COMP_FULL;
+                       if (ec_asprintf(&comp_str, "%s%s", input,
+                                       &de->d_name[bname_len]) < 0)
+                               goto fail;
+                       if (ec_asprintf(&disp_str, "%s", de->d_name) < 0)
+                               goto fail;
+               }
+               if (ec_comp_add_item(comp, gen_node, &item,
+                                               type, input, comp_str) < 0)
+                       goto out;
+
+               /* fix the display string: we don't want to display the full
+                * path. */
+               if (ec_comp_item_set_display(item, disp_str) < 0)
+                       goto out;
+
+               item = NULL;
+               ec_free(comp_str);
+               comp_str = NULL;
+               ec_free(disp_str);
+               disp_str = NULL;
+       }
+out:
+       ec_free(comp_str);
+       ec_free(disp_str);
+       ec_free(dname);
+       ec_free(bname);
+       if (dir != NULL)
+               node->closedir(dir);
+
+       return 0;
+
+fail:
+       ec_free(comp_str);
+       ec_free(disp_str);
+       ec_free(dname);
+       ec_free(bname);
+       if (dir != NULL)
+               node->closedir(dir);
+
+       return -1;
+}
+
+static int
+ec_node_file_init_priv(struct ec_node *gen_node)
+{
+       struct ec_node_file *node = (struct ec_node_file *)gen_node;
+
+       node->lstat = lstat;
+       node->opendir = opendir;
+       node->readdir = readdir;
+       node->dirfd = dirfd;
+       node->fstatat = fstatat;
+
+       return 0;
+}
+
+static struct ec_node_type ec_node_file_type = {
+       .name = "file",
+       .parse = ec_node_file_parse,
+       .complete = ec_node_file_complete,
+       .size = sizeof(struct ec_node_file),
+       .init_priv = ec_node_file_init_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_file_type);
+
+/* LCOV_EXCL_START */
+static int
+test_lstat(const char *pathname, struct stat *buf)
+{
+       if (!strcmp(pathname, "/tmp/toto/")) {
+               struct stat st = { .st_mode = S_IFDIR };
+               memcpy(buf, &st, sizeof(*buf));
+               return 0;
+       }
+
+       errno = ENOENT;
+       return -1;
+}
+
+static DIR *
+test_opendir(const char *name)
+{
+       int *p;
+
+       if (strcmp(name, "/tmp/toto/")) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       p = malloc(sizeof(int));
+       if (p)
+               *p = 0;
+
+       return (DIR *)p;
+}
+
+static struct dirent *
+test_readdir(DIR *dirp)
+{
+       static struct dirent de[] = {
+               { .d_type = DT_DIR, .d_name = ".." },
+               { .d_type = DT_DIR, .d_name = "." },
+               { .d_type = DT_REG, .d_name = "bar" },
+               { .d_type = DT_UNKNOWN, .d_name = "bar2" },
+               { .d_type = DT_REG, .d_name = "foo" },
+               { .d_type = DT_DIR, .d_name = "titi" },
+               { .d_type = DT_UNKNOWN, .d_name = "tutu" },
+               { .d_name = "" },
+       };
+       int *p = (int *)dirp;
+       struct dirent *ret = &de[*p];
+
+       if (!strcmp(ret->d_name, ""))
+               return NULL;
+
+       *p = *p + 1;
+
+       return ret;
+}
+
+static int
+test_closedir(DIR *dirp)
+{
+       free(dirp);
+       return 0;
+}
+
+static int
+test_dirfd(DIR *dirp)
+{
+       int *p = (int *)dirp;
+       return *p;
+}
+
+static int
+test_fstatat(int dirfd, const char *pathname, struct stat *buf,
+       int flags)
+{
+       (void)dirfd;
+       (void)flags;
+
+       if (!strcmp(pathname, "bar2")) {
+               struct stat st = { .st_mode = S_IFREG };
+               memcpy(buf, &st, sizeof(*buf));
+               return 0;
+       } else if (!strcmp(pathname, "tutu")) {
+               struct stat st = { .st_mode = S_IFDIR };
+               memcpy(buf, &st, sizeof(*buf));
+               return 0;
+       }
+
+       errno = ENOENT;
+       return -1;
+}
+
+static int
+ec_node_file_override_functions(struct ec_node *gen_node)
+{
+       struct ec_node_file *node = (struct ec_node_file *)gen_node;
+
+       node->lstat = test_lstat;
+       node->opendir = test_opendir;
+       node->readdir = test_readdir;
+       node->closedir = test_closedir;
+       node->dirfd = test_dirfd;
+       node->fstatat = test_fstatat;
+
+       return 0;
+}
+
+static int ec_node_file_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("file", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       ec_node_file_override_functions(node);
+
+       /* any string matches */
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+
+       /* test completion */
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp/toto/t", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE_PARTIAL(node,
+               "/tmp/toto/t", EC_NODE_ENDLIST,
+               "/tmp/toto/titi/", "/tmp/toto/tutu/", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp/toto/f", EC_NODE_ENDLIST,
+               "/tmp/toto/foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp/toto/b", EC_NODE_ENDLIST,
+               "/tmp/toto/bar", "/tmp/toto/bar2", EC_NODE_ENDLIST);
+
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_file_test = {
+       .name = "node_file",
+       .test = ec_node_file_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_file_test);
diff --git a/libecoli/ecoli_node_file.h b/libecoli/ecoli_node_file.h
new file mode 100644 (file)
index 0000000..5760902
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_FILE_
+#define ECOLI_NODE_FILE_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_file(const char *id, const char *file);
+
+/* file is duplicated */
+int ec_node_file_set_str(struct ec_node *node, const char *file);
+
+#endif
diff --git a/libecoli/ecoli_node_helper.c b/libecoli/ecoli_node_helper.c
new file mode 100644 (file)
index 0000000..9ec7e89
--- /dev/null
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_config.h>
+#include <ecoli_node.h>
+#include <ecoli_node_helper.h>
+
+struct ec_node **
+ec_node_config_node_list_to_table(const struct ec_config *config,
+                               size_t *len)
+{
+       struct ec_node **table = NULL;
+       struct ec_config *child;
+       size_t n, i;
+
+       *len = 0;
+
+       if (config == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if (ec_config_get_type(config) != EC_CONFIG_TYPE_LIST) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       n = 0;
+       TAILQ_FOREACH(child, &config->list, next) {
+               if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
+                       errno = EINVAL;
+                       return NULL;
+               }
+               n++;
+       }
+
+       table = ec_malloc(n * sizeof(*table));
+       if (table == NULL)
+               goto fail;
+
+       n = 0;
+       TAILQ_FOREACH(child, &config->list, next) {
+               table[n] = ec_node_clone(child->node);
+               n++;
+       }
+
+       *len = n;
+
+       return table;
+
+fail:
+       if (table != NULL) {
+               for (i = 0; i < n; i++)
+                       ec_node_free(table[i]);
+       }
+       ec_free(table);
+
+       return NULL;
+}
+
+struct ec_config *
+ec_node_config_node_list_from_vargs(va_list ap)
+{
+       struct ec_config *list = NULL;
+       struct ec_node *node = va_arg(ap, struct ec_node *);
+
+       list = ec_config_list();
+       if (list == NULL)
+               goto fail;
+
+       for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *)) {
+               if (node == NULL)
+                       goto fail;
+
+               if (ec_config_list_add(list, ec_config_node(node)) < 0)
+                       goto fail;
+       }
+
+       return list;
+
+fail:
+       for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *))
+               ec_node_free(node);
+       ec_config_free(list);
+
+       return NULL;
+}
diff --git a/libecoli/ecoli_node_helper.h b/libecoli/ecoli_node_helper.h
new file mode 100644 (file)
index 0000000..9dbf519
--- /dev/null
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Helpers that are commonly used in nodes.
+ */
+
+#ifndef ECOLI_NODE_HELPERS_
+#define ECOLI_NODE_HELPERS
+
+struct ec_node;
+
+/**
+ * Build a node table from a node list in a ec_config.
+ *
+ * The function takes a node configuration as parameter, which must be a
+ * node list. From it, a node table is built. A reference is taken for
+ * each node.
+ *
+ * On error, no reference is taken.
+ *
+ * @param config
+ *   The configuration (type must be a list of nodes). If it is
+ *   NULL, an error is returned.
+ * @param len
+ *   The length of the allocated table on success, or 0 on error.
+ * @return
+ *   The allocated node table, that must be freed by the caller:
+ *   each entry must be freed with ec_node_free() and the table
+ *   with ec_free(). On error, NULL is returned and errno is set.
+ */
+struct ec_node **
+ec_node_config_node_list_to_table(const struct ec_config *config,
+                               size_t *len);
+
+/**
+ * Build a list of config nodes from variable arguments.
+ *
+ * The va_list argument is a list of pointer to ec_node structures,
+ * terminated with EC_NODE_ENDLIST.
+ *
+ * This helper is used by nodes that contain a list of nodes,
+ * like "seq", "or", ...
+ *
+ * @param ap
+ *   List of pointer to ec_node structures, terminated with
+ *   EC_NODE_ENDLIST.
+ * @return
+ *   A pointer to an ec_config structure. In this case, the
+ *   nodes will be freed when the config structure will be freed.
+ *   On error, NULL is returned (and errno is set), and the
+ *   nodes are freed.
+ */
+struct ec_config *
+ec_node_config_node_list_from_vargs(va_list ap);
+
+#endif
diff --git a/libecoli/ecoli_node_int.c b/libecoli/ecoli_node_int.c
new file mode 100644 (file)
index 0000000..9b56e22
--- /dev/null
@@ -0,0 +1,501 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_int.h>
+#include <ecoli_test.h>
+
+EC_LOG_TYPE_REGISTER(node_int);
+
+/* common to int and uint */
+struct ec_node_int_uint {
+       struct ec_node gen;
+       bool is_signed;
+       bool check_min;
+       bool check_max;
+       union {
+               int64_t min;
+               uint64_t umin;
+       };
+       union {
+               int64_t max;
+               uint64_t umax;
+       };
+       unsigned int base;
+};
+
+/* XXX to utils.c ? */
+static int parse_llint(struct ec_node_int_uint *node, const char *str,
+       int64_t *val)
+{
+       char *endptr;
+       int save_errno = errno;
+
+       errno = 0;
+       *val = strtoll(str, &endptr, node->base);
+
+       if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
+                       (errno != 0 && *val == 0))
+               return -1;
+
+       if (node->check_min && *val < node->min) {
+               errno = ERANGE;
+               return -1;
+       }
+
+       if (node->check_max && *val > node->max) {
+               errno = ERANGE;
+               return -1;
+       }
+
+       if (*endptr != 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       errno = save_errno;
+       return 0;
+}
+
+static int parse_ullint(struct ec_node_int_uint *node, const char *str,
+                       uint64_t *val)
+{
+       char *endptr;
+       int save_errno = errno;
+
+       /* since a negative input is silently converted to a positive
+        * one by strtoull(), first check that it is positive */
+       if (strchr(str, '-'))
+               return -1;
+
+       errno = 0;
+       *val = strtoull(str, &endptr, node->base);
+
+       if ((errno == ERANGE && *val == ULLONG_MAX) ||
+                       (errno != 0 && *val == 0))
+               return -1;
+
+       if (node->check_min && *val < node->umin)
+               return -1;
+
+       if (node->check_max && *val > node->umax)
+               return -1;
+
+       if (*endptr != 0)
+               return -1;
+
+       errno = save_errno;
+       return 0;
+}
+
+static int ec_node_int_uint_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       const char *str;
+       uint64_t u64;
+       int64_t i64;
+
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       str = ec_strvec_val(strvec, 0);
+       if (node->is_signed) {
+               if (parse_llint(node, str, &i64) < 0)
+                       return EC_PARSE_NOMATCH;
+       } else {
+               if (parse_ullint(node, str, &u64) < 0)
+                       return EC_PARSE_NOMATCH;
+       }
+       return 1;
+}
+
+static int
+ec_node_uint_init_priv(struct ec_node *gen_node)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+
+       node->is_signed = true;
+
+       return 0;
+}
+
+static const struct ec_config_schema ec_node_int_schema[] = {
+       {
+               .key = "min",
+               .desc = "The minimum valid value (included).",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .key = "max",
+               .desc = "The maximum valid value (included).",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .key = "base",
+               .desc = "The base to use. If unset or 0, try to guess.",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_int_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       const struct ec_config *min_value = NULL;
+       const struct ec_config *max_value = NULL;
+       const struct ec_config *base_value = NULL;
+       char *s = NULL;
+
+       min_value = ec_config_dict_get(config, "min");
+       max_value = ec_config_dict_get(config, "max");
+       base_value = ec_config_dict_get(config, "base");
+
+       if (min_value && max_value && min_value->i64 > max_value->i64) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (min_value != NULL) {
+               node->check_min = true;
+               node->min = min_value->i64;
+       } else {
+               node->check_min = false;
+       }
+       if (max_value != NULL) {
+               node->check_max = true;
+               node->max = max_value->i64;
+       } else {
+               node->check_min = false;
+       }
+       if (base_value != NULL)
+               node->base = base_value->u64;
+       else
+               node->base = 0;
+
+       return 0;
+
+fail:
+       ec_free(s);
+       return -1;
+}
+
+static struct ec_node_type ec_node_int_type = {
+       .name = "int",
+       .schema = ec_node_int_schema,
+       .set_config = ec_node_int_set_config,
+       .parse = ec_node_int_uint_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_int_uint),
+       .init_priv = ec_node_uint_init_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_int_type);
+
+struct ec_node *ec_node_int(const char *id, int64_t min,
+       int64_t max, unsigned int base)
+{
+       struct ec_config *config = NULL;
+       struct ec_node *gen_node = NULL;
+       int ret;
+
+       gen_node = ec_node_from_type(&ec_node_int_type, id);
+       if (gen_node == NULL)
+               return NULL;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "min", ec_config_i64(min));
+       if (ret < 0)
+               goto fail;
+       ret = ec_config_dict_set(config, "max", ec_config_i64(max));
+       if (ret < 0)
+               goto fail;
+       ret = ec_config_dict_set(config, "base", ec_config_u64(base));
+       if (ret < 0)
+               goto fail;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL;
+       if (ret < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(gen_node);
+       return NULL;
+}
+
+static const struct ec_config_schema ec_node_uint_schema[] = {
+       {
+               .key = "min",
+               .desc = "The minimum valid value (included).",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .key = "max",
+               .desc = "The maximum valid value (included).",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .key = "base",
+               .desc = "The base to use. If unset or 0, try to guess.",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_uint_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       const struct ec_config *min_value = NULL;
+       const struct ec_config *max_value = NULL;
+       const struct ec_config *base_value = NULL;
+       char *s = NULL;
+
+       min_value = ec_config_dict_get(config, "min");
+       max_value = ec_config_dict_get(config, "max");
+       base_value = ec_config_dict_get(config, "base");
+
+       if (min_value && max_value && min_value->u64 > max_value->u64) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (min_value != NULL) {
+               node->check_min = true;
+               node->min = min_value->u64;
+       } else {
+               node->check_min = false;
+       }
+       if (max_value != NULL) {
+               node->check_max = true;
+               node->max = max_value->u64;
+       } else {
+               node->check_min = false;
+       }
+       if (base_value != NULL)
+               node->base = base_value->u64;
+       else
+               node->base = 0;
+
+       return 0;
+
+fail:
+       ec_free(s);
+       return -1;
+}
+
+static struct ec_node_type ec_node_uint_type = {
+       .name = "uint",
+       .schema = ec_node_uint_schema,
+       .set_config = ec_node_uint_set_config,
+       .parse = ec_node_int_uint_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_int_uint),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_uint_type);
+
+struct ec_node *ec_node_uint(const char *id, uint64_t min,
+       uint64_t max, unsigned int base)
+{
+       struct ec_config *config = NULL;
+       struct ec_node *gen_node = NULL;
+       int ret;
+
+       gen_node = ec_node_from_type(&ec_node_uint_type, id);
+       if (gen_node == NULL)
+               return NULL;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "min", ec_config_u64(min));
+       if (ret < 0)
+               goto fail;
+       ret = ec_config_dict_set(config, "max", ec_config_u64(max));
+       if (ret < 0)
+               goto fail;
+       ret = ec_config_dict_set(config, "base", ec_config_u64(base));
+       if (ret < 0)
+               goto fail;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL;
+       if (ret < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(gen_node);
+       return NULL;
+}
+
+int ec_node_int_getval(const struct ec_node *gen_node, const char *str,
+                       int64_t *result)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       int ret;
+
+       ret = ec_node_check_type(gen_node, &ec_node_int_type);
+       if (ret < 0)
+               return ret;
+
+       if (parse_llint(node, str, result) < 0)
+               return -1;
+
+       return 0;
+}
+
+int ec_node_uint_getval(const struct ec_node *gen_node, const char *str,
+                       uint64_t *result)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       int ret;
+
+       ret = ec_node_check_type(gen_node, &ec_node_uint_type);
+       if (ret < 0)
+               return ret;
+
+       if (parse_ullint(node, str, result) < 0)
+               return -1;
+
+       return 0;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_int_testcase(void)
+{
+       struct ec_parse *p;
+       struct ec_node *node;
+       const char *s;
+       int testres = 0;
+       uint64_t u64;
+       int64_t i64;
+
+       node = ec_node_uint(EC_NO_ID, 1, 256, 0);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "256", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "0x100");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, " 1");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "-1");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x101");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x100000000000000000");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "4r");
+
+       p = ec_node_parse(node, "1");
+       s = ec_strvec_val(ec_parse_strvec(p), 0);
+       testres |= EC_TEST_CHECK(s != NULL &&
+               ec_node_uint_getval(node, s, &u64) == 0 &&
+               u64 == 1, "bad integer value");
+       ec_parse_free(p);
+
+       p = ec_node_parse(node, "10");
+       s = ec_strvec_val(ec_parse_strvec(p), 0);
+       testres |= EC_TEST_CHECK(s != NULL &&
+               ec_node_uint_getval(node, s, &u64) == 0 &&
+               u64 == 10, "bad integer value");
+       ec_parse_free(p);
+       ec_node_free(node);
+
+       node = ec_node_int(EC_NO_ID, -1, LLONG_MAX, 16);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "0");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "-1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "7fffffffffffffff");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "0x7fffffffffffffff");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x8000000000000000");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "-2");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "4r");
+
+       p = ec_node_parse(node, "10");
+       s = ec_strvec_val(ec_parse_strvec(p), 0);
+       testres |= EC_TEST_CHECK(s != NULL &&
+               ec_node_int_getval(node, s, &i64) == 0 &&
+               i64 == 16, "bad integer value");
+       ec_parse_free(p);
+       ec_node_free(node);
+
+       node = ec_node_int(EC_NO_ID, LLONG_MIN, 0, 10);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "0");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "-1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "-9223372036854775808");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x0");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "1");
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_int(EC_NO_ID, 0, 10, 0);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "1", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_int_test = {
+       .name = "node_int",
+       .test = ec_node_int_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_int_test);
diff --git a/libecoli/ecoli_node_int.h b/libecoli/ecoli_node_int.h
new file mode 100644 (file)
index 0000000..b64c60c
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_INT_
+#define ECOLI_NODE_INT_
+
+#include <stdint.h>
+
+#include <ecoli_node.h>
+
+/* ec_node("int", ...) can be used too
+ * default is no limit, base 10 */
+
+struct ec_node *ec_node_int(const char *id, int64_t min,
+                       int64_t max, unsigned int base);
+
+int ec_node_int_getval(const struct ec_node *node, const char *str,
+                       int64_t *result);
+
+
+
+struct ec_node *ec_node_uint(const char *id, uint64_t min,
+                       uint64_t max, unsigned int base);
+
+int ec_node_uint_getval(const struct ec_node *node, const char *str,
+                       uint64_t *result);
+
+
+#endif
diff --git a/libecoli/ecoli_node_many.c b/libecoli/ecoli_node_many.c
new file mode 100644 (file)
index 0000000..1c91f85
--- /dev/null
@@ -0,0 +1,300 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_option.h>
+#include <ecoli_node_many.h>
+
+EC_LOG_TYPE_REGISTER(node_many);
+
+struct ec_node_many {
+       struct ec_node gen;
+       unsigned int min;
+       unsigned int max;
+       struct ec_node *child;
+};
+
+static int ec_node_many_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+       struct ec_parse *child_parse;
+       struct ec_strvec *childvec = NULL;
+       size_t off = 0, count;
+       int ret;
+
+       for (count = 0; node->max == 0 || count < node->max; count++) {
+               childvec = ec_strvec_ndup(strvec, off,
+                       ec_strvec_len(strvec) - off);
+               if (childvec == NULL)
+                       goto fail;
+
+               ret = ec_node_parse_child(node->child, state, childvec);
+               if (ret < 0)
+                       goto fail;
+
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if (ret == EC_PARSE_NOMATCH)
+                       break;
+
+               /* it matches an empty strvec, no need to continue */
+               if (ret == 0) {
+                       child_parse = ec_parse_get_last_child(state);
+                       ec_parse_unlink_child(state, child_parse);
+                       ec_parse_free(child_parse);
+                       break;
+               }
+
+               off += ret;
+       }
+
+       if (count < node->min) {
+               ec_parse_free_children(state);
+               return EC_PARSE_NOMATCH;
+       }
+
+       return off;
+
+fail:
+       ec_strvec_free(childvec);
+       return -1;
+}
+
+static int
+__ec_node_many_complete(struct ec_node_many *node, unsigned int max,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_parse *parse = ec_comp_get_state(comp);
+       struct ec_strvec *childvec = NULL;
+       unsigned int i;
+       int ret;
+
+       /* first, try to complete with the child node */
+       ret = ec_node_complete_child(node->child, comp, strvec);
+       if (ret < 0)
+               goto fail;
+
+       /* we're done, we reached the max number of nodes */
+       if (max == 1)
+               return 0;
+
+       /* if there is a maximum, decrease it before recursion */
+       if (max != 0)
+               max--;
+
+       /* then, if the node matches the beginning of the strvec, try to
+        * complete the rest */
+       for (i = 0; i < ec_strvec_len(strvec); i++) {
+               childvec = ec_strvec_ndup(strvec, 0, i);
+               if (childvec == NULL)
+                       goto fail;
+
+               ret = ec_node_parse_child(node->child, parse, childvec);
+               if (ret < 0)
+                       goto fail;
+
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if ((unsigned int)ret != i) {
+                       if (ret != EC_PARSE_NOMATCH)
+                               ec_parse_del_last_child(parse);
+                       continue;
+               }
+
+               childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i);
+               if (childvec == NULL) {
+                       ec_parse_del_last_child(parse);
+                       goto fail;
+               }
+
+               ret = __ec_node_many_complete(node, max, comp, childvec);
+               ec_parse_del_last_child(parse);
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if (ret < 0)
+                       goto fail;
+       }
+
+       return 0;
+
+fail:
+       ec_strvec_free(childvec);
+       return -1;
+}
+
+static int
+ec_node_many_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+
+       return __ec_node_many_complete(node, node->max, comp,
+                               strvec);
+}
+
+static void ec_node_many_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+
+       ec_node_free(node->child);
+}
+
+static size_t
+ec_node_many_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_many_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_many_type = {
+       .name = "many",
+       .parse = ec_node_many_parse,
+       .complete = ec_node_many_complete,
+       .size = sizeof(struct ec_node_many),
+       .free_priv = ec_node_many_free_priv,
+       .get_children_count = ec_node_many_get_children_count,
+       .get_child = ec_node_many_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_many_type);
+
+struct ec_node *ec_node_many(const char *id, struct ec_node *child,
+       unsigned int min, unsigned int max)
+{
+       struct ec_node_many *node = NULL;
+
+       if (child == NULL)
+               return NULL;
+
+       node = (struct ec_node_many *)ec_node_from_type(&ec_node_many_type, id);
+       if (node == NULL) {
+               ec_node_free(child);
+               return NULL;
+       }
+
+       node->child = child;
+       node->min = min;
+       node->max = max;
+
+       return &node->gen;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_many_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 0, 0);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       ec_node_free(node);
+
+       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 0);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+       ec_node_free(node);
+
+       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 2);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 2, 4);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "foo", "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "foo", "foo", "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "foo", "foo", "foo", "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_many_test = {
+       .name = "node_many",
+       .test = ec_node_many_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_many_test);
diff --git a/libecoli/ecoli_node_many.h b/libecoli/ecoli_node_many.h
new file mode 100644 (file)
index 0000000..14250d1
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_MANY_
+#define ECOLI_NODE_MANY_
+
+/*
+ * if min == max == 0, there is no limit
+ */
+struct ec_node *ec_node_many(const char *id, struct ec_node *child,
+       unsigned int min, unsigned int max);
+
+#endif
diff --git a/libecoli/ecoli_node_none.c b/libecoli/ecoli_node_none.c
new file mode 100644 (file)
index 0000000..dba9ebf
--- /dev/null
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_none.h>
+
+EC_LOG_TYPE_REGISTER(node_none);
+
+struct ec_node_none {
+       struct ec_node gen;
+};
+
+static int ec_node_none_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)state;
+       (void)strvec;
+
+       return EC_PARSE_NOMATCH;
+}
+
+static int
+ec_node_none_complete(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)comp;
+       (void)strvec;
+
+       return 0;
+}
+
+static struct ec_node_type ec_node_none_type = {
+       .name = "none",
+       .parse = ec_node_none_parse,
+       .complete = ec_node_none_complete,
+       .size = sizeof(struct ec_node_none),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_none_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_none_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("none", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+       ec_node_free(node);
+
+       /* never completes */
+       node = ec_node("none", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_none_test = {
+       .name = "node_none",
+       .test = ec_node_none_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_none_test);
diff --git a/libecoli/ecoli_node_none.h b/libecoli/ecoli_node_none.h
new file mode 100644 (file)
index 0000000..842f211
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * This node does not match anything
+ */
+
+#ifndef ECOLI_NODE_ANY_
+#define ECOLI_NODE_ANY_
+
+#endif
diff --git a/libecoli/ecoli_node_once.c b/libecoli/ecoli_node_once.c
new file mode 100644 (file)
index 0000000..6309878
--- /dev/null
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_once.h>
+
+EC_LOG_TYPE_REGISTER(node_once);
+
+struct ec_node_once {
+       struct ec_node gen;
+       struct ec_node *child;
+};
+
+static unsigned int
+count_node(struct ec_parse *parse, const struct ec_node *node)
+{
+       struct ec_parse *child;
+       unsigned int count = 0;
+
+       if (parse == NULL)
+               return 0;
+
+       if (ec_parse_get_node(parse) == node)
+               count++;
+
+       EC_PARSE_FOREACH_CHILD(child, parse)
+               count += count_node(child, node);
+
+       return count;
+}
+
+static int
+ec_node_once_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+       unsigned int count;
+
+       /* count the number of occurences of the node: if already parsed,
+        * do not match
+        */
+       count = count_node(ec_parse_get_root(state), node->child);
+       if (count > 0)
+               return EC_PARSE_NOMATCH;
+
+       return ec_node_parse_child(node->child, state, strvec);
+}
+
+static int
+ec_node_once_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+       struct ec_parse *parse = ec_comp_get_state(comp);
+       unsigned int count;
+       int ret;
+
+       /* count the number of occurences of the node: if already parsed,
+        * do not match
+        */
+       count = count_node(ec_parse_get_root(parse), node->child);
+       if (count > 0)
+               return 0;
+
+       ret = ec_node_complete_child(node->child, comp, strvec);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static void ec_node_once_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+
+       ec_node_free(node->child);
+}
+
+static size_t
+ec_node_once_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_once_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_once_type = {
+       .name = "once",
+       .parse = ec_node_once_parse,
+       .complete = ec_node_once_complete,
+       .size = sizeof(struct ec_node_once),
+       .free_priv = ec_node_once_free_priv,
+       .get_children_count = ec_node_once_get_children_count,
+       .get_child = ec_node_once_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_once_type);
+
+int ec_node_once_set(struct ec_node *gen_node, struct ec_node *child)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+
+       if (gen_node == NULL || child == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (ec_node_check_type(gen_node, &ec_node_once_type) < 0)
+               goto fail;
+
+       node->child = child;
+
+       return 0;
+
+fail:
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *ec_node_once(const char *id, struct ec_node *child)
+{
+       struct ec_node *gen_node = NULL;
+
+       if (child == NULL)
+               return NULL;
+
+       gen_node = ec_node_from_type(&ec_node_once_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       ec_node_once_set(gen_node, child);
+       child = NULL;
+
+       return gen_node;
+
+fail:
+       ec_node_free(child);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_once_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_many(EC_NO_ID,
+                       EC_NODE_OR(EC_NO_ID,
+                               ec_node_once(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")),
+                               ec_node_str(EC_NO_ID, "bar")
+                               ), 0, 0
+               );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "foox");
+
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "b", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", "", EC_NODE_ENDLIST,
+               "foo", "bar", EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_once_test = {
+       .name = "node_once",
+       .test = ec_node_once_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_once_test);
diff --git a/libecoli/ecoli_node_once.h b/libecoli/ecoli_node_once.h
new file mode 100644 (file)
index 0000000..a610a83
--- /dev/null
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_ONCE_
+#define ECOLI_NODE_ONCE_
+
+#include <ecoli_node.h>
+
+/* This node behaves like its child, but prevent from parsing it several
+ * times.
+ *
+ * Example:
+ *   many(
+ *     or(
+ *       once(str("foo")),
+ *       str("bar")))
+ *
+ *   Matches: [], ["foo", "bar"], ["bar", "bar"], ["foo", "bar", "bar"], ...
+ *   But not: ["foo", "foo"], ["foo", "bar", "foo"], ...
+ */
+
+/* on error, child is *not* freed */
+struct ec_node *ec_node_once(const char *id, struct ec_node *child);
+
+/* on error, child is *not* freed */
+int ec_node_once_set(struct ec_node *node, struct ec_node *child);
+
+#endif
diff --git a/libecoli/ecoli_node_option.c b/libecoli/ecoli_node_option.c
new file mode 100644 (file)
index 0000000..ab4f352
--- /dev/null
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_str.h>
+#include <ecoli_test.h>
+#include <ecoli_node_option.h>
+
+EC_LOG_TYPE_REGISTER(node_option);
+
+struct ec_node_option {
+       struct ec_node gen;
+       struct ec_node *child;
+};
+
+static int
+ec_node_option_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+       int ret;
+
+       ret = ec_node_parse_child(node->child, state, strvec);
+       if (ret < 0)
+               return ret;
+
+       if (ret == EC_PARSE_NOMATCH)
+               return 0;
+
+       return ret;
+}
+
+static int
+ec_node_option_complete(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       return ec_node_complete_child(node->child, comp, strvec);
+}
+
+static void ec_node_option_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       ec_node_free(node->child);
+}
+
+static size_t
+ec_node_option_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_option_get_child(const struct ec_node *gen_node, size_t i,
+       struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_option_type = {
+       .name = "option",
+       .parse = ec_node_option_parse,
+       .complete = ec_node_option_complete,
+       .size = sizeof(struct ec_node_option),
+       .free_priv = ec_node_option_free_priv,
+       .get_children_count = ec_node_option_get_children_count,
+       .get_child = ec_node_option_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_option_type);
+
+int ec_node_option_set(struct ec_node *gen_node, struct ec_node *child)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       if (gen_node == NULL || child == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (ec_node_check_type(gen_node, &ec_node_option_type) < 0)
+               goto fail;
+
+       node->child = child;
+
+       return 0;
+
+fail:
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *ec_node_option(const char *id, struct ec_node *child)
+{
+       struct ec_node *gen_node = NULL;
+
+       if (child == NULL)
+               goto fail;
+
+       gen_node = ec_node_from_type(&ec_node_option_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       ec_node_option_set(gen_node, child);
+       child = NULL;
+
+       return gen_node;
+
+fail:
+       ec_node_free(child);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_option_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"));
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"));
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "b", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_option_test = {
+       .name = "node_option",
+       .test = ec_node_option_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_option_test);
diff --git a/libecoli/ecoli_node_option.h b/libecoli/ecoli_node_option.h
new file mode 100644 (file)
index 0000000..9d67480
--- /dev/null
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_OPTION_
+#define ECOLI_NODE_OPTION_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_option(const char *id, struct ec_node *node);
+int ec_node_option_set(struct ec_node *gen_node, struct ec_node *child);
+
+#endif
diff --git a/libecoli/ecoli_node_or.c b/libecoli/ecoli_node_or.c
new file mode 100644 (file)
index 0000000..2bf8fc7
--- /dev/null
@@ -0,0 +1,347 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_node_helper.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_str.h>
+#include <ecoli_test.h>
+
+EC_LOG_TYPE_REGISTER(node_or);
+
+struct ec_node_or {
+       struct ec_node gen;
+       struct ec_node **table;
+       size_t len;
+};
+
+static int
+ec_node_or_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < node->len; i++) {
+               ret = ec_node_parse_child(node->table[i], state, strvec);
+               if (ret == EC_PARSE_NOMATCH)
+                       continue;
+               return ret;
+       }
+
+       return EC_PARSE_NOMATCH;
+}
+
+static int
+ec_node_or_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       int ret;
+       size_t n;
+
+       for (n = 0; n < node->len; n++) {
+               ret = ec_node_complete_child(node->table[n],
+                                       comp, strvec);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void ec_node_or_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       size_t i;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = NULL;
+       node->len = 0;
+}
+
+static const struct ec_config_schema ec_node_or_subschema[] = {
+       {
+               .desc = "A child node which is part of the choice.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema ec_node_or_schema[] = {
+       {
+               .key = "children",
+               .desc = "The list of children nodes defining the choice "
+               "elements.",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = ec_node_or_subschema,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_or_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       struct ec_node **table = NULL;
+       size_t i, len = 0;
+
+       table = ec_node_config_node_list_to_table(
+               ec_config_dict_get(config, "children"), &len);
+       if (table == NULL)
+               goto fail;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = table;
+       node->len = len;
+
+       return 0;
+
+fail:
+       for (i = 0; i < len; i++)
+               ec_node_free(table[i]);
+       ec_free(table);
+       return -1;
+}
+
+static size_t
+ec_node_or_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       return node->len;
+}
+
+static int
+ec_node_or_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+
+       if (i >= node->len)
+               return -1;
+
+       *child = node->table[i];
+       /* each child node is referenced twice: once in the config and
+        * once in the node->table[] */
+       *refs = 2;
+       return 0;
+}
+
+static struct ec_node_type ec_node_or_type = {
+       .name = "or",
+       .schema = ec_node_or_schema,
+       .set_config = ec_node_or_set_config,
+       .parse = ec_node_or_parse,
+       .complete = ec_node_or_complete,
+       .size = sizeof(struct ec_node_or),
+       .free_priv = ec_node_or_free_priv,
+       .get_children_count = ec_node_or_get_children_count,
+       .get_child = ec_node_or_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_or_type);
+
+int ec_node_or_add(struct ec_node *gen_node, struct ec_node *child)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL, *children;
+       int ret;
+
+       assert(node != NULL);
+
+       /* XXX factorize this code in a helper */
+
+       if (ec_node_check_type(gen_node, &ec_node_or_type) < 0)
+               goto fail;
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       children = ec_config_dict_get(config, "children");
+       if (children == NULL) {
+               children = ec_config_list();
+               if (children == NULL)
+                       goto fail;
+
+               if (ec_config_dict_set(config, "children", children) < 0)
+                       goto fail; /* children list is freed on error */
+       }
+
+       if (ec_config_list_add(children, ec_config_node(child)) < 0) {
+               child = NULL;
+               goto fail;
+       }
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *__ec_node_or(const char *id, ...)
+{
+       struct ec_config *config = NULL, *children = NULL;
+       struct ec_node *gen_node = NULL;
+       struct ec_node *child;
+       va_list ap;
+       int ret;
+
+       va_start(ap, id);
+       child = va_arg(ap, struct ec_node *);
+
+       gen_node = ec_node_from_type(&ec_node_or_type, id);
+       if (gen_node == NULL)
+               goto fail_free_children;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail_free_children;
+
+       children = ec_config_list();
+       if (children == NULL)
+               goto fail_free_children;
+
+       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) {
+               if (child == NULL)
+                       goto fail_free_children;
+
+               if (ec_config_list_add(children, ec_config_node(child)) < 0) {
+                       child = NULL;
+                       goto fail_free_children;
+               }
+       }
+
+       if (ec_config_dict_set(config, "children", children) < 0) {
+               children = NULL; /* freed */
+               goto fail;
+       }
+       children = NULL;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       va_end(ap);
+
+       return gen_node;
+
+fail_free_children:
+       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *))
+               ec_node_free(child);
+fail:
+       ec_node_free(gen_node); /* will also free added children */
+       ec_config_free(children);
+       ec_config_free(config);
+       va_end(ap);
+
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_or_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = EC_NODE_OR(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_str(EC_NO_ID, "bar")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, " ");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foox");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "toto");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       ec_node_free(node);
+
+       /* test completion */
+       node = EC_NODE_OR(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_str(EC_NO_ID, "bar"),
+               ec_node_str(EC_NO_ID, "bar2"),
+               ec_node_str(EC_NO_ID, "toto"),
+               ec_node_str(EC_NO_ID, "titi")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "b", EC_NODE_ENDLIST,
+               "bar", "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", EC_NODE_ENDLIST,
+               "bar", "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "t", EC_NODE_ENDLIST,
+               "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "to", EC_NODE_ENDLIST,
+               "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_or_test = {
+       .name = "node_or",
+       .test = ec_node_or_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_or_test);
diff --git a/libecoli/ecoli_node_or.h b/libecoli/ecoli_node_or.h
new file mode 100644 (file)
index 0000000..db115b2
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_OR_
+#define ECOLI_NODE_OR_
+
+#include <ecoli_node.h>
+
+#define EC_NODE_OR(args...) __ec_node_or(args, EC_NODE_ENDLIST)
+
+/* list must be terminated with EC_NODE_ENDLIST */
+/* all nodes given in the list will be freed when freeing this one */
+/* avoid using this function directly, prefer the macro EC_NODE_OR() or
+ * ec_node_or() + ec_node_or_add() */
+struct ec_node *__ec_node_or(const char *id, ...);
+
+struct ec_node *ec_node_or(const char *id);
+
+/* child is consumed */
+int ec_node_or_add(struct ec_node *node, struct ec_node *child);
+
+
+#endif
diff --git a/libecoli/ecoli_node_re.c b/libecoli/ecoli_node_re.c
new file mode 100644 (file)
index 0000000..6ac5182
--- /dev/null
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <regex.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_re.h>
+
+EC_LOG_TYPE_REGISTER(node_re);
+
+struct ec_node_re {
+       struct ec_node gen;
+       char *re_str;
+       regex_t re;
+};
+
+static int
+ec_node_re_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_re *node = (struct ec_node_re *)gen_node;
+       const char *str;
+       regmatch_t pos;
+
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       str = ec_strvec_val(strvec, 0);
+       if (regexec(&node->re, str, 1, &pos, 0) != 0)
+               return EC_PARSE_NOMATCH;
+       if (pos.rm_so != 0 || pos.rm_eo != (int)strlen(str))
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+static void ec_node_re_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_re *node = (struct ec_node_re *)gen_node;
+
+       if (node->re_str != NULL) {
+               ec_free(node->re_str);
+               regfree(&node->re);
+       }
+}
+
+static struct ec_node_type ec_node_re_type = {
+       .name = "re",
+       .parse = ec_node_re_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_re),
+       .free_priv = ec_node_re_free_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_re_type);
+
+int ec_node_re_set_regexp(struct ec_node *gen_node, const char *str)
+{
+       struct ec_node_re *node = (struct ec_node_re *)gen_node;
+       char *str_copy = NULL;
+       regex_t re;
+       int ret;
+
+       EC_CHECK_ARG(str != NULL, -1, EINVAL);
+
+       str_copy = ec_strdup(str);
+       if (str_copy == NULL)
+               goto fail;
+
+       ret = regcomp(&re, str_copy, REG_EXTENDED);
+       if (ret != 0) {
+               if (ret == REG_ESPACE)
+                       errno = ENOMEM;
+               else
+                       errno = EINVAL;
+               goto fail;
+       }
+
+       if (node->re_str != NULL) {
+               ec_free(node->re_str);
+               regfree(&node->re);
+       }
+       node->re_str = str_copy;
+       node->re = re;
+
+       return 0;
+
+fail:
+       ec_free(str_copy);
+       return -1;
+}
+
+struct ec_node *ec_node_re(const char *id, const char *re_str)
+{
+       struct ec_node *gen_node = NULL;
+
+       gen_node = ec_node_from_type(&ec_node_re_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       if (ec_node_re_set_regexp(gen_node, re_str) < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_re_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_re(EC_NO_ID, "fo+|bar");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_re_test = {
+       .name = "node_re",
+       .test = ec_node_re_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_re_test);
diff --git a/libecoli/ecoli_node_re.h b/libecoli/ecoli_node_re.h
new file mode 100644 (file)
index 0000000..bc3a317
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_RE_
+#define ECOLI_NODE_RE_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_re(const char *id, const char *str);
+
+/* re is duplicated */
+int ec_node_re_set_regexp(struct ec_node *node, const char *re);
+
+#endif
diff --git a/libecoli/ecoli_node_re_lex.c b/libecoli/ecoli_node_re_lex.c
new file mode 100644 (file)
index 0000000..f5a6c37
--- /dev/null
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <regex.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_complete.h>
+#include <ecoli_parse.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_int.h>
+#include <ecoli_node_re_lex.h>
+
+EC_LOG_TYPE_REGISTER(node_re_lex);
+
+struct regexp_pattern {
+       char *pattern;
+       regex_t r;
+       bool keep;
+};
+
+struct ec_node_re_lex {
+       struct ec_node gen;
+       struct ec_node *child;
+       struct regexp_pattern *table;
+       size_t len;
+};
+
+static struct ec_strvec *
+tokenize(struct regexp_pattern *table, size_t table_len, const char *str)
+{
+       struct ec_strvec *strvec = NULL;
+       char *dup = NULL;
+       char c;
+       size_t len, off = 0;
+       size_t i;
+       int ret;
+       regmatch_t pos;
+
+       dup = ec_strdup(str);
+       if (dup == NULL)
+               goto fail;
+
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       len = strlen(dup);
+       while (off < len) {
+               for (i = 0; i < table_len; i++) {
+                       ret = regexec(&table[i].r, &dup[off], 1, &pos, 0);
+                       if (ret != 0)
+                               continue;
+                       if (pos.rm_so != 0 || pos.rm_eo == 0) {
+                               ret = -1;
+                               continue;
+                       }
+
+                       if (table[i].keep == 0)
+                               break;
+
+                       c = dup[pos.rm_eo + off];
+                       dup[pos.rm_eo + off] = '\0';
+                       EC_LOG(EC_LOG_DEBUG, "re_lex match <%s>\n", &dup[off]);
+                       if (ec_strvec_add(strvec, &dup[off]) < 0)
+                               goto fail;
+
+                       dup[pos.rm_eo + off] = c;
+                       break;
+               }
+
+               if (ret != 0)
+                       goto fail;
+
+               off += pos.rm_eo;
+       }
+
+       ec_free(dup);
+       return strvec;
+
+fail:
+       ec_free(dup);
+       ec_strvec_free(strvec);
+       return NULL;
+}
+
+static int
+ec_node_re_lex_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+       struct ec_strvec *new_vec = NULL;
+       struct ec_parse *child_parse;
+       const char *str;
+       int ret;
+
+       if (ec_strvec_len(strvec) == 0) {
+               new_vec = ec_strvec();
+       } else {
+               str = ec_strvec_val(strvec, 0);
+               new_vec = tokenize(node->table, node->len, str);
+       }
+       if (new_vec == NULL)
+               goto fail;
+
+       ret = ec_node_parse_child(node->child, state, new_vec);
+       if (ret < 0)
+               goto fail;
+
+       if ((unsigned)ret == ec_strvec_len(new_vec)) {
+               ret = 1;
+       } else if (ret != EC_PARSE_NOMATCH) {
+               child_parse = ec_parse_get_last_child(state);
+               ec_parse_unlink_child(state, child_parse);
+               ec_parse_free(child_parse);
+               ret = EC_PARSE_NOMATCH;
+       }
+
+       ec_strvec_free(new_vec);
+       new_vec = NULL;
+
+       return ret;
+
+ fail:
+       ec_strvec_free(new_vec);
+       return -1;
+}
+
+static void ec_node_re_lex_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+       unsigned int i;
+
+       ec_node_free(node->child);
+       for (i = 0; i < node->len; i++) {
+               ec_free(node->table[i].pattern);
+               regfree(&node->table[i].r);
+       }
+
+       ec_free(node->table);
+}
+
+static size_t
+ec_node_re_lex_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_re_lex_get_child(const struct ec_node *gen_node, size_t i,
+                       struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_re_lex_type = {
+       .name = "re_lex",
+       .parse = ec_node_re_lex_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_re_lex),
+       .free_priv = ec_node_re_lex_free_priv,
+       .get_children_count = ec_node_re_lex_get_children_count,
+       .get_child = ec_node_re_lex_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_re_lex_type);
+
+int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+       struct regexp_pattern *table;
+       int ret;
+       char *pat_dup = NULL;
+
+       pat_dup = ec_strdup(pattern);
+       if (pat_dup == NULL)
+               goto fail;
+
+       table = ec_realloc(node->table, sizeof(*table) * (node->len + 1));
+       if (table == NULL)
+               goto fail;
+
+       ret = regcomp(&table[node->len].r, pattern, REG_EXTENDED);
+       if (ret != 0) {
+               EC_LOG(EC_LOG_ERR,
+                       "Regular expression <%s> compilation failed: %d\n",
+                       pattern, ret);
+               if (ret == REG_ESPACE)
+                       errno = ENOMEM;
+               else
+                       errno = EINVAL;
+
+               goto fail;
+       }
+
+       table[node->len].pattern = pat_dup;
+       table[node->len].keep = keep;
+       node->len++;
+       node->table = table;
+
+       return 0;
+
+fail:
+       ec_free(pat_dup);
+       return -1;
+}
+
+struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child)
+{
+       struct ec_node_re_lex *node = NULL;
+
+       if (child == NULL)
+               return NULL;
+
+       node = (struct ec_node_re_lex *)ec_node_from_type(&ec_node_re_lex_type, id);
+       if (node == NULL) {
+               ec_node_free(child);
+               return NULL;
+       }
+
+       node->child = child;
+
+       return &node->gen;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_re_lex_testcase(void)
+{
+       struct ec_node *node;
+       int ret, testres = 0;
+
+       node = ec_node_re_lex(EC_NO_ID,
+               ec_node_many(EC_NO_ID,
+                       EC_NODE_OR(EC_NO_ID,
+                               ec_node_str(EC_NO_ID, "foo"),
+                               ec_node_str(EC_NO_ID, "bar"),
+                               ec_node_int(EC_NO_ID, 0, 1000, 0)
+                       ), 0, 0
+               )
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+
+       ret = ec_node_re_lex_add(node, "[a-zA-Z]+", 1);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "[0-9]+", 1);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "=", 1);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "-", 1);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "\\+", 1);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "[       ]+", 0);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       if (ret != 0) {
+               EC_LOG(EC_LOG_ERR, "cannot add regexp to node\n");
+               ec_node_free(node);
+               return -1;
+       }
+
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo bar  324 bar234");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar324");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
+
+       /* no completion */
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_re_lex_test = {
+       .name = "node_re_lex",
+       .test = ec_node_re_lex_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_re_lex_test);
diff --git a/libecoli/ecoli_node_re_lex.h b/libecoli/ecoli_node_re_lex.h
new file mode 100644 (file)
index 0000000..632627c
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_RE_LEX_
+#define ECOLI_NODE_RE_LEX_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child);
+
+int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep);
+
+#endif
diff --git a/libecoli/ecoli_node_seq.c b/libecoli/ecoli_node_seq.c
new file mode 100644 (file)
index 0000000..ff0c5de
--- /dev/null
@@ -0,0 +1,442 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_helper.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_option.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_seq.h>
+
+EC_LOG_TYPE_REGISTER(node_seq);
+
+struct ec_node_seq {
+       struct ec_node gen;
+       struct ec_node **table;
+       size_t len;
+};
+
+static int
+ec_node_seq_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       struct ec_strvec *childvec = NULL;
+       size_t len = 0;
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < node->len; i++) {
+               childvec = ec_strvec_ndup(strvec, len,
+                       ec_strvec_len(strvec) - len);
+               if (childvec == NULL)
+                       goto fail;
+
+               ret = ec_node_parse_child(node->table[i], state, childvec);
+               if (ret < 0)
+                       goto fail;
+
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if (ret == EC_PARSE_NOMATCH) {
+                       ec_parse_free_children(state);
+                       return EC_PARSE_NOMATCH;
+               }
+
+               len += ret;
+       }
+
+       return len;
+
+fail:
+       ec_strvec_free(childvec);
+       return -1;
+}
+
+static int
+__ec_node_seq_complete(struct ec_node **table, size_t table_len,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_parse *parse = ec_comp_get_state(comp);
+       struct ec_strvec *childvec = NULL;
+       unsigned int i;
+       int ret;
+
+       if (table_len == 0)
+               return 0;
+
+       /*
+        * Example of completion for a sequence node = [n1,n2] and an
+        * input = [a,b,c,d]:
+        *
+        * result = complete(n1, [a,b,c,d]) +
+        *    complete(n2, [b,c,d]) if n1 matches [a] +
+        *    complete(n2, [c,d]) if n1 matches [a,b] +
+        *    complete(n2, [d]) if n1 matches [a,b,c] +
+        *    complete(n2, []) if n1 matches [a,b,c,d]
+        */
+
+       /* first, try to complete with the first node of the table */
+       ret = ec_node_complete_child(table[0], comp, strvec);
+       if (ret < 0)
+               goto fail;
+
+       /* then, if the first node of the table matches the beginning of the
+        * strvec, try to complete the rest */
+       for (i = 0; i < ec_strvec_len(strvec); i++) {
+               childvec = ec_strvec_ndup(strvec, 0, i);
+               if (childvec == NULL)
+                       goto fail;
+
+               ret = ec_node_parse_child(table[0], parse, childvec);
+               if (ret < 0)
+                       goto fail;
+
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if ((unsigned int)ret != i) {
+                       if (ret != EC_PARSE_NOMATCH)
+                               ec_parse_del_last_child(parse);
+                       continue;
+               }
+
+               childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i);
+               if (childvec == NULL) {
+                       ec_parse_del_last_child(parse);
+                       goto fail;
+               }
+
+               ret = __ec_node_seq_complete(&table[1],
+                                       table_len - 1,
+                                       comp, childvec);
+               ec_parse_del_last_child(parse);
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if (ret < 0)
+                       goto fail;
+       }
+
+       return 0;
+
+fail:
+       ec_strvec_free(childvec);
+       return -1;
+}
+
+static int
+ec_node_seq_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+
+       return __ec_node_seq_complete(node->table, node->len, comp,
+                               strvec);
+}
+
+static void ec_node_seq_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       size_t i;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = NULL;
+       node->len = 0;
+}
+
+static const struct ec_config_schema ec_node_seq_subschema[] = {
+       {
+               .desc = "A child node which is part of the sequence.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema ec_node_seq_schema[] = {
+       {
+               .key = "children",
+               .desc = "The list of children nodes, to be parsed in sequence.",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = ec_node_seq_subschema,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_seq_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       struct ec_node **table = NULL;
+       size_t i, len = 0;
+
+       table = ec_node_config_node_list_to_table(
+               ec_config_dict_get(config, "children"), &len);
+       if (table == NULL)
+               goto fail;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = table;
+       node->len = len;
+
+       return 0;
+
+fail:
+       for (i = 0; i < len; i++)
+               ec_node_free(table[i]);
+       ec_free(table);
+       return -1;
+}
+
+static size_t
+ec_node_seq_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       return node->len;
+}
+
+static int
+ec_node_seq_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+
+       if (i >= node->len)
+               return -1;
+
+       *child = node->table[i];
+       /* each child node is referenced twice: once in the config and
+        * once in the node->table[] */
+       *refs = 2;
+       return 0;
+}
+
+static struct ec_node_type ec_node_seq_type = {
+       .name = "seq",
+       .schema = ec_node_seq_schema,
+       .set_config = ec_node_seq_set_config,
+       .parse = ec_node_seq_parse,
+       .complete = ec_node_seq_complete,
+       .size = sizeof(struct ec_node_seq),
+       .free_priv = ec_node_seq_free_priv,
+       .get_children_count = ec_node_seq_get_children_count,
+       .get_child = ec_node_seq_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_seq_type);
+
+int ec_node_seq_add(struct ec_node *gen_node, struct ec_node *child)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL, *children;
+       int ret;
+
+       assert(node != NULL);
+
+       /* XXX factorize this code in a helper */
+
+       if (ec_node_check_type(gen_node, &ec_node_seq_type) < 0)
+               goto fail;
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       children = ec_config_dict_get(config, "children");
+       if (children == NULL) {
+               children = ec_config_list();
+               if (children == NULL)
+                       goto fail;
+
+               if (ec_config_dict_set(config, "children", children) < 0)
+                       goto fail; /* children list is freed on error */
+       }
+
+       if (ec_config_list_add(children, ec_config_node(child)) < 0) {
+               child = NULL;
+               goto fail;
+       }
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *__ec_node_seq(const char *id, ...)
+{
+       struct ec_config *config = NULL, *children = NULL;
+       struct ec_node *gen_node = NULL;
+       struct ec_node *child;
+       va_list ap;
+       int ret;
+
+       va_start(ap, id);
+       child = va_arg(ap, struct ec_node *);
+
+       gen_node = ec_node_from_type(&ec_node_seq_type, id);
+       if (gen_node == NULL)
+               goto fail_free_children;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail_free_children;
+
+       children = ec_config_list();
+       if (children == NULL)
+               goto fail_free_children;
+
+       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) {
+               if (child == NULL)
+                       goto fail_free_children;
+
+               if (ec_config_list_add(children, ec_config_node(child)) < 0) {
+                       child = NULL;
+                       goto fail_free_children;
+               }
+       }
+
+       if (ec_config_dict_set(config, "children", children) < 0) {
+               children = NULL; /* freed */
+               goto fail;
+       }
+       children = NULL;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       va_end(ap);
+
+       return gen_node;
+
+fail_free_children:
+       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *))
+               ec_node_free(child);
+fail:
+       ec_node_free(gen_node); /* will also free added children */
+       ec_config_free(children);
+       ec_config_free(config);
+       va_end(ap);
+
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_seq_testcase(void)
+{
+       struct ec_node *node = NULL;
+       int testres = 0;
+
+       node = EC_NODE_SEQ(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_str(EC_NO_ID, "bar")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "toto");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foox", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "barx");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "", "foo");
+
+       testres |= (ec_node_seq_add(node, ec_node_str(EC_NO_ID, "grr")) < 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "grr");
+
+       ec_node_free(node);
+
+       /* test completion */
+       node = EC_NODE_SEQ(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "toto")),
+               ec_node_str(EC_NO_ID, "bar")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "", EC_NODE_ENDLIST,
+               "bar", "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "t", EC_NODE_ENDLIST,
+               "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "b", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "bar", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foobarx", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_seq_test = {
+       .name = "node_seq",
+       .test = ec_node_seq_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_seq_test);
diff --git a/libecoli/ecoli_node_seq.h b/libecoli/ecoli_node_seq.h
new file mode 100644 (file)
index 0000000..21c96b1
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_SEQ_
+#define ECOLI_NODE_SEQ_
+
+#include <ecoli_node.h>
+
+#define EC_NODE_SEQ(args...) __ec_node_seq(args, EC_NODE_ENDLIST)
+
+/* list must be terminated with EC_NODE_ENDLIST */
+/* all nodes given in the list will be freed when freeing this one */
+/* avoid using this function directly, prefer the macro EC_NODE_SEQ() or
+ * ec_node_seq() + ec_node_seq_add() */
+struct ec_node *__ec_node_seq(const char *id, ...);
+
+struct ec_node *ec_node_seq(const char *id);
+
+/* child is consumed */
+int ec_node_seq_add(struct ec_node *node, struct ec_node *child);
+
+#endif
diff --git a/libecoli/ecoli_node_sh_lex.c b/libecoli/ecoli_node_sh_lex.c
new file mode 100644 (file)
index 0000000..e27f21b
--- /dev/null
@@ -0,0 +1,491 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_string.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_option.h>
+#include <ecoli_node_sh_lex.h>
+
+EC_LOG_TYPE_REGISTER(node_sh_lex);
+
+struct ec_node_sh_lex {
+       struct ec_node gen;
+       struct ec_node *child;
+};
+
+static size_t eat_spaces(const char *str)
+{
+       size_t i = 0;
+
+       /* skip spaces */
+       while (isblank(str[i]))
+               i++;
+
+       return i;
+}
+
+/*
+ * 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 = 1, d = 0;
+       char quote = str[0];
+       char *dst;
+       int closed = 0;
+
+       dst = ec_malloc(n);
+       if (dst == NULL) {
+               errno = ENOMEM;
+               return NULL;
+       }
+
+       /* copy string and remove quotes */
+       while (s < n && d < n && str[s] != '\0') {
+               if (str[s] == '\\' && str[s+1] == quote) {
+                       dst[d++] = quote;
+                       s += 2;
+                       continue;
+               }
+               if (str[s] == '\\' && str[s+1] == '\\') {
+                       dst[d++] = '\\';
+                       s += 2;
+                       continue;
+               }
+               if (str[s] == quote) {
+                       s++;
+                       closed = 1;
+                       break;
+               }
+               dst[d++] = str[s++];
+       }
+
+       /* not enough room in dst buffer (should not happen) */
+       if (d >= n) {
+               ec_free(dst);
+               errno = EMSGSIZE;
+               return NULL;
+       }
+
+       /* quote not closed */
+       if (closed == 0) {
+               if (missing_quote != NULL)
+                       *missing_quote = str[0];
+               if (allow_missing_quote == 0) {
+                       ec_free(dst);
+                       errno = EBADMSG;
+                       return NULL;
+               }
+       }
+       dst[d++] = '\0';
+
+       return dst;
+}
+
+static size_t eat_quoted_str(const char *str)
+{
+       size_t i = 0;
+       char quote = str[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 size_t eat_str(const char *str)
+{
+       size_t i = 0;
+
+       /* eat chars until we find a quote, space, or end of string  */
+       while (!isblank(str[i]) && str[i] != '\0' &&
+                       str[i] != '"' && str[i] != '\'')
+               i++;
+
+       return i;
+}
+
+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;
+
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       while (str[off] != '\0') {
+               if (missing_quote != NULL)
+                       *missing_quote = '\0';
+               len = eat_spaces(&str[off]);
+               if (len > 0)
+                       last_is_space = 1;
+               off += len;
+
+               len = 0;
+               suboff = off;
+               while (str[suboff] != '\0') {
+                       if (missing_quote != NULL)
+                               *missing_quote = '\0';
+                       last_is_space = 0;
+                       if (str[suboff] == '"' || str[suboff] == '\'') {
+                               sublen = eat_quoted_str(&str[suboff]);
+                               word = unquote_str(&str[suboff], sublen,
+                                       allow_missing_quote, missing_quote);
+                       } else {
+                               sublen = eat_str(&str[suboff]);
+                               if (sublen == 0)
+                                       break;
+                               word = ec_strndup(&str[suboff], sublen);
+                       }
+
+                       if (word == NULL)
+                               goto fail;
+
+                       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;
+               }
+
+               off += len;
+       }
+
+       /* in completion mode, append an empty string in the vector if
+        * the input string ends with space */
+       if (completion && last_is_space) {
+               if (ec_strvec_add(strvec, "") < 0)
+                       goto fail;
+       }
+
+       return strvec;
+
+ fail:
+       ec_free(word);
+       ec_free(concat);
+       ec_strvec_free(strvec);
+       return NULL;
+}
+
+static int
+ec_node_sh_lex_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+       struct ec_strvec *new_vec = NULL;
+       struct ec_parse *child_parse;
+       const char *str;
+       int ret;
+
+       if (ec_strvec_len(strvec) == 0) {
+               new_vec = ec_strvec();
+       } else {
+               str = ec_strvec_val(strvec, 0);
+               new_vec = tokenize(str, 0, 0, NULL);
+       }
+       if (new_vec == NULL && errno == EBADMSG) /* quotes not closed */
+               return EC_PARSE_NOMATCH;
+       if (new_vec == NULL)
+               goto fail;
+
+       ret = ec_node_parse_child(node->child, state, new_vec);
+       if (ret < 0)
+               goto fail;
+
+       if ((unsigned)ret == ec_strvec_len(new_vec)) {
+               ret = 1;
+       } else if (ret != EC_PARSE_NOMATCH) {
+               child_parse = ec_parse_get_last_child(state);
+               ec_parse_unlink_child(state, child_parse);
+               ec_parse_free(child_parse);
+               ret = EC_PARSE_NOMATCH;
+       }
+
+       ec_strvec_free(new_vec);
+       new_vec = NULL;
+
+       return ret;
+
+ fail:
+       ec_strvec_free(new_vec);
+       return -1;
+}
+
+static int
+ec_node_sh_lex_complete(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+       struct ec_comp *tmp_comp = NULL;
+       struct ec_strvec *new_vec = NULL;
+       struct ec_comp_iter *iter = NULL;
+       struct ec_comp_item *item = NULL;
+       char *new_str = NULL;
+       const char *str;
+       char missing_quote = '\0';
+       int ret;
+
+       if (ec_strvec_len(strvec) != 1)
+               return 0;
+
+       str = ec_strvec_val(strvec, 0);
+       new_vec = tokenize(str, 1, 1, &missing_quote);
+       if (new_vec == NULL)
+               goto fail;
+
+       /* we will store the completions in a temporary struct, because
+        * we want to update them (ex: add missing quotes) */
+       tmp_comp = ec_comp(ec_comp_get_state(comp));
+       if (tmp_comp == NULL)
+               goto fail;
+
+       ret = ec_node_complete_child(node->child, tmp_comp, new_vec);
+       if (ret < 0)
+               goto fail;
+
+       /* add missing quote for full completions  */
+       if (missing_quote != '\0') {
+               iter = ec_comp_iter(tmp_comp, EC_COMP_FULL);
+               if (iter == NULL)
+                       goto fail;
+               while ((item = ec_comp_iter_next(iter)) != NULL) {
+                       str = ec_comp_item_get_str(item);
+                       if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str,
+                                       missing_quote) < 0) {
+                               new_str = NULL;
+                               goto fail;
+                       }
+                       if (ec_comp_item_set_str(item, new_str) < 0)
+                               goto fail;
+                       ec_free(new_str);
+                       new_str = NULL;
+
+                       str = ec_comp_item_get_completion(item);
+                       if (ec_asprintf(&new_str, "%s%c", str,
+                                       missing_quote) < 0) {
+                               new_str = NULL;
+                               goto fail;
+                       }
+                       if (ec_comp_item_set_completion(item, new_str) < 0)
+                               goto fail;
+                       ec_free(new_str);
+                       new_str = NULL;
+               }
+       }
+
+       ec_comp_iter_free(iter);
+       ec_strvec_free(new_vec);
+
+       ec_comp_merge(comp, tmp_comp);
+
+       return 0;
+
+ fail:
+       ec_comp_free(tmp_comp);
+       ec_comp_iter_free(iter);
+       ec_strvec_free(new_vec);
+       ec_free(new_str);
+
+       return -1;
+}
+
+static void ec_node_sh_lex_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+
+       ec_node_free(node->child);
+}
+
+static size_t
+ec_node_sh_lex_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_sh_lex_get_child(const struct ec_node *gen_node, size_t i,
+       struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *refs = 1;
+       *child = node->child;
+       return 0;
+}
+
+static struct ec_node_type ec_node_sh_lex_type = {
+       .name = "sh_lex",
+       .parse = ec_node_sh_lex_parse,
+       .complete = ec_node_sh_lex_complete,
+       .size = sizeof(struct ec_node_sh_lex),
+       .free_priv = ec_node_sh_lex_free_priv,
+       .get_children_count = ec_node_sh_lex_get_children_count,
+       .get_child = ec_node_sh_lex_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
+
+struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
+{
+       struct ec_node_sh_lex *node = NULL;
+
+       if (child == NULL)
+               return NULL;
+
+       node = (struct ec_node_sh_lex *)ec_node_from_type(&ec_node_sh_lex_type, id);
+       if (node == NULL) {
+               ec_node_free(child);
+               return NULL;
+       }
+
+       node->child = child;
+
+       return &node->gen;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_sh_lex_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_sh_lex(EC_NO_ID,
+               EC_NODE_SEQ(EC_NO_ID,
+                       ec_node_str(EC_NO_ID, "foo"),
+                       ec_node_option(EC_NO_ID,
+                               ec_node_str(EC_NO_ID, "toto")
+                       ),
+                       ec_node_str(EC_NO_ID, "bar")
+               )
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_sh_lex(EC_NO_ID,
+               EC_NODE_SEQ(EC_NO_ID,
+                       ec_node_str(EC_NO_ID, "foo"),
+                       ec_node_option(EC_NO_ID,
+                               ec_node_str(EC_NO_ID, "toto")
+                       ),
+                       ec_node_str(EC_NO_ID, "bar"),
+                       ec_node_str(EC_NO_ID, "titi")
+               )
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               " ", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo ", EC_NODE_ENDLIST,
+               "bar", "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo t", EC_NODE_ENDLIST,
+               "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo b", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo bar", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo bar ", EC_NODE_ENDLIST,
+               "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo toto bar ", EC_NODE_ENDLIST,
+               "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo barx", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo 'b", EC_NODE_ENDLIST,
+               "'bar'", EC_NODE_ENDLIST);
+
+       ec_node_free(node);
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_sh_lex_test = {
+       .name = "node_sh_lex",
+       .test = ec_node_sh_lex_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_sh_lex_test);
diff --git a/libecoli/ecoli_node_sh_lex.h b/libecoli/ecoli_node_sh_lex.h
new file mode 100644 (file)
index 0000000..d45b998
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_SHLEX_
+#define ECOLI_NODE_SHLEX_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child);
+
+#endif
diff --git a/libecoli/ecoli_node_space.c b/libecoli/ecoli_node_space.c
new file mode 100644 (file)
index 0000000..761ed76
--- /dev/null
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_space.h>
+
+EC_LOG_TYPE_REGISTER(node_space);
+
+struct ec_node_space {
+       struct ec_node gen;
+};
+
+static int
+ec_node_space_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       const char *str;
+       size_t len = 0;
+
+       (void)state;
+       (void)gen_node;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       str = ec_strvec_val(strvec, 0);
+       while (isspace(str[len]))
+               len++;
+       if (len == 0 || len != strlen(str))
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+static struct ec_node_type ec_node_space_type = {
+       .name = "space",
+       .parse = ec_node_space_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_space),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_space_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_space_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("space", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, " ");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, " ", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo ");
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node("space", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       /* never completes whatever the input */
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               " ", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_space_test = {
+       .name = "space",
+       .test = ec_node_space_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_space_test);
diff --git a/libecoli/ecoli_node_space.h b/libecoli/ecoli_node_space.h
new file mode 100644 (file)
index 0000000..0dd6202
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * This node matches one string in the vector if it is only composed of
+ * spaces, as interpreted by isspace().
+ */
+
+#ifndef ECOLI_NODE_SPACE_
+#define ECOLI_NODE_SPACE_
+
+/* no API for now, since there is no specific configuration for this node */
+
+#endif
diff --git a/libecoli/ecoli_node_str.c b/libecoli/ecoli_node_str.c
new file mode 100644 (file)
index 0000000..d53ea39
--- /dev/null
@@ -0,0 +1,274 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_str.h>
+
+EC_LOG_TYPE_REGISTER(node_str);
+
+struct ec_node_str {
+       struct ec_node gen;
+       char *string;
+       unsigned len;
+};
+
+static int
+ec_node_str_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+       const char *str;
+
+       (void)state;
+
+       if (node->string == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       str = ec_strvec_val(strvec, 0);
+       if (strcmp(str, node->string) != 0)
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+static int
+ec_node_str_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+       const char *str;
+       size_t n = 0;
+
+       if (ec_strvec_len(strvec) != 1)
+               return 0;
+
+       str = ec_strvec_val(strvec, 0);
+       for (n = 0; n < node->len; n++) {
+               if (str[n] != node->string[n])
+                       break;
+       }
+
+       /* no completion */
+       if (str[n] != '\0')
+               return EC_PARSE_NOMATCH;
+
+       if (ec_comp_add_item(comp, gen_node, NULL, EC_COMP_FULL,
+                                       str, node->string) < 0)
+               return -1;
+
+       return 0;
+}
+
+static const char *ec_node_str_desc(const struct ec_node *gen_node)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+
+       return node->string;
+}
+
+static void ec_node_str_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+
+       ec_free(node->string);
+}
+
+static const struct ec_config_schema ec_node_str_schema[] = {
+       {
+               .key = "string",
+               .desc = "The string to match.",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_str_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+       const struct ec_config *value = NULL;
+       char *s = NULL;
+
+       value = ec_config_dict_get(config, "string");
+       if (value == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       s = ec_strdup(value->string);
+       if (s == NULL)
+               goto fail;
+
+       ec_free(node->string);
+       node->string = s;
+       node->len = strlen(node->string);
+
+       return 0;
+
+fail:
+       ec_free(s);
+       return -1;
+}
+
+static struct ec_node_type ec_node_str_type = {
+       .name = "str",
+       .schema = ec_node_str_schema,
+       .set_config = ec_node_str_set_config,
+       .parse = ec_node_str_parse,
+       .complete = ec_node_str_complete,
+       .desc = ec_node_str_desc,
+       .size = sizeof(struct ec_node_str),
+       .free_priv = ec_node_str_free_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_str_type);
+
+int ec_node_str_set_str(struct ec_node *gen_node, const char *str)
+{
+       struct ec_config *config = NULL;
+       int ret;
+
+       if (ec_node_check_type(gen_node, &ec_node_str_type) < 0)
+               goto fail;
+
+       if (str == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "string", ec_config_string(str));
+       if (ret < 0)
+               goto fail;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL;
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       return -1;
+}
+
+struct ec_node *ec_node_str(const char *id, const char *str)
+{
+       struct ec_node *gen_node = NULL;
+
+       gen_node = ec_node_from_type(&ec_node_str_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       if (ec_node_str_set_str(gen_node, str) < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_str_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_str(EC_NO_ID, "foo");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK(!strcmp(ec_node_desc(node), "foo"),
+               "Invalid node description.");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       ec_node_free(node);
+
+       node = ec_node_str(EC_NO_ID, "Здравствуйте");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте",
+               "John!");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       ec_node_free(node);
+
+       /* an empty string node always matches */
+       node = ec_node_str(EC_NO_ID, "");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_str(EC_NO_ID, "foo");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_str_test = {
+       .name = "node_str",
+       .test = ec_node_str_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_str_test);
diff --git a/libecoli/ecoli_node_str.h b/libecoli/ecoli_node_str.h
new file mode 100644 (file)
index 0000000..8a8634f
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_STR_
+#define ECOLI_NODE_STR_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_str(const char *id, const char *str);
+
+/* str is duplicated */
+int ec_node_str_set_str(struct ec_node *node, const char *str);
+
+#endif
diff --git a/libecoli/ecoli_node_subset.c b/libecoli/ecoli_node_subset.c
new file mode 100644 (file)
index 0000000..e3184ef
--- /dev/null
@@ -0,0 +1,430 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_subset.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_or.h>
+#include <ecoli_test.h>
+
+EC_LOG_TYPE_REGISTER(node_subset);
+
+struct ec_node_subset {
+       struct ec_node gen;
+       struct ec_node **table;
+       unsigned int len;
+};
+
+struct parse_result {
+       size_t parse_len;           /* number of parsed nodes */
+       size_t len;                 /* consumed strings */
+};
+
+/* recursively find the longest list of nodes that matches: the state is
+ * updated accordingly. */
+static int
+__ec_node_subset_parse(struct parse_result *out, struct ec_node **table,
+               size_t table_len, struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node **child_table;
+       struct ec_strvec *childvec = NULL;
+       size_t i, j, len = 0;
+       struct parse_result best_result, result;
+       struct ec_parse *best_parse = NULL;
+       int ret;
+
+       if (table_len == 0)
+               return 0;
+
+       memset(&best_result, 0, sizeof(best_result));
+
+       child_table = ec_calloc(table_len - 1, sizeof(*child_table));
+       if (child_table == NULL)
+               goto fail;
+
+       for (i = 0; i < table_len; i++) {
+               /* try to parse elt i */
+               ret = ec_node_parse_child(table[i], state, strvec);
+               if (ret < 0)
+                       goto fail;
+
+               if (ret == EC_PARSE_NOMATCH)
+                       continue;
+
+               /* build a new table without elt i */
+               for (j = 0; j < table_len; j++) {
+                       if (j < i)
+                               child_table[j] = table[j];
+                       else if (j > i)
+                               child_table[j - 1] = table[j];
+               }
+
+               /* build a new strvec (ret is the len of matched strvec) */
+               len = ret;
+               childvec = ec_strvec_ndup(strvec, len,
+                                       ec_strvec_len(strvec) - len);
+               if (childvec == NULL)
+                       goto fail;
+
+               memset(&result, 0, sizeof(result));
+               ret = __ec_node_subset_parse(&result, child_table,
+                                       table_len - 1, state, childvec);
+               ec_strvec_free(childvec);
+               childvec = NULL;
+               if (ret < 0)
+                       goto fail;
+
+               /* if result is not the best, ignore */
+               if (result.parse_len < best_result.parse_len) {
+                       memset(&result, 0, sizeof(result));
+                       ec_parse_del_last_child(state);
+                       continue;
+               }
+
+               /* replace the previous best result */
+               ec_parse_free(best_parse);
+               best_parse = ec_parse_get_last_child(state);
+               ec_parse_unlink_child(state, best_parse);
+
+               best_result.parse_len = result.parse_len + 1;
+               best_result.len = len + result.len;
+
+               memset(&result, 0, sizeof(result));
+       }
+
+       *out = best_result;
+       ec_free(child_table);
+       if (best_parse != NULL)
+               ec_parse_link_child(state, best_parse);
+
+       return 0;
+
+ fail:
+       ec_parse_free(best_parse);
+       ec_strvec_free(childvec);
+       ec_free(child_table);
+       return -1;
+}
+
+static int
+ec_node_subset_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+       struct ec_parse *parse = NULL;
+       struct parse_result result;
+       int ret;
+
+       memset(&result, 0, sizeof(result));
+
+       ret = __ec_node_subset_parse(&result, node->table,
+                               node->len, state, strvec);
+       if (ret < 0)
+               goto fail;
+
+       /* if no child node matches, return a matching empty strvec */
+       if (result.parse_len == 0)
+               return 0;
+
+       return result.len;
+
+ fail:
+       ec_parse_free(parse);
+       return ret;
+}
+
+static int
+__ec_node_subset_complete(struct ec_node **table, size_t table_len,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_parse *parse = ec_comp_get_state(comp);
+       struct ec_strvec *childvec = NULL;
+       struct ec_node *save;
+       size_t i, len;
+       int ret;
+
+       /*
+        * example with table = [a, b, c]
+        * subset_complete([a,b,c], strvec) returns:
+        *   complete(a, strvec) + complete(b, strvec) + complete(c, strvec) +
+        *   + __subset_complete([b, c], childvec) if a matches
+        *   + __subset_complete([a, c], childvec) if b matches
+        *   + __subset_complete([a, b], childvec) if c matches
+        */
+
+       /* first, try to complete with each node of the table */
+       for (i = 0; i < table_len; i++) {
+               if (table[i] == NULL)
+                       continue;
+
+               ret = ec_node_complete_child(table[i],
+                                       comp, strvec);
+               if (ret < 0)
+                       goto fail;
+       }
+
+       /* then, if a node matches, advance in strvec and try to complete with
+        * all the other nodes */
+       for (i = 0; i < table_len; i++) {
+               if (table[i] == NULL)
+                       continue;
+
+               ret = ec_node_parse_child(table[i], parse, strvec);
+               if (ret < 0)
+                       goto fail;
+
+               if (ret == EC_PARSE_NOMATCH)
+                       continue;
+
+               len = ret;
+               childvec = ec_strvec_ndup(strvec, len,
+                                       ec_strvec_len(strvec) - len);
+               if (childvec == NULL) {
+                       ec_parse_del_last_child(parse);
+                       goto fail;
+               }
+
+               save = table[i];
+               table[i] = NULL;
+               ret = __ec_node_subset_complete(table, table_len,
+                                               comp, childvec);
+               table[i] = save;
+               ec_strvec_free(childvec);
+               childvec = NULL;
+               ec_parse_del_last_child(parse);
+
+               if (ret < 0)
+                       goto fail;
+       }
+
+       return 0;
+
+fail:
+       return -1;
+}
+
+static int
+ec_node_subset_complete(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+
+       return __ec_node_subset_complete(node->table, node->len, comp,
+                                       strvec);
+}
+
+static void ec_node_subset_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+       size_t i;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+}
+
+static size_t
+ec_node_subset_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+       return node->len;
+}
+
+static int
+ec_node_subset_get_child(const struct ec_node *gen_node, size_t i,
+                       struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+
+       if (i >= node->len)
+               return -1;
+
+       *child = node->table[i];
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_subset_type = {
+       .name = "subset",
+       .parse = ec_node_subset_parse,
+       .complete = ec_node_subset_complete,
+       .size = sizeof(struct ec_node_subset),
+       .free_priv = ec_node_subset_free_priv,
+       .get_children_count = ec_node_subset_get_children_count,
+       .get_child = ec_node_subset_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_subset_type);
+
+int ec_node_subset_add(struct ec_node *gen_node, struct ec_node *child)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+       struct ec_node **table;
+
+       assert(node != NULL); // XXX specific assert for it, like in libyang
+
+       if (child == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (ec_node_check_type(gen_node, &ec_node_subset_type) < 0)
+               goto fail;
+
+       table = ec_realloc(node->table, (node->len + 1) * sizeof(*node->table));
+       if (table == NULL) {
+               ec_node_free(child);
+               return -1;
+       }
+
+       node->table = table;
+       table[node->len] = child;
+       node->len++;
+
+       return 0;
+
+fail:
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *__ec_node_subset(const char *id, ...)
+{
+       struct ec_node *gen_node = NULL;
+       struct ec_node_subset *node = NULL;
+       struct ec_node *child;
+       va_list ap;
+       int fail = 0;
+
+       va_start(ap, id);
+
+       gen_node = ec_node_from_type(&ec_node_subset_type, id);
+       node = (struct ec_node_subset *)gen_node;
+       if (node == NULL)
+               fail = 1;;
+
+       for (child = va_arg(ap, struct ec_node *);
+            child != EC_NODE_ENDLIST;
+            child = va_arg(ap, struct ec_node *)) {
+
+               /* on error, don't quit the loop to avoid leaks */
+               if (fail == 1 || child == NULL ||
+                               ec_node_subset_add(gen_node, child) < 0) {
+                       fail = 1;
+                       ec_node_free(child);
+               }
+       }
+
+       if (fail == 1)
+               goto fail;
+
+       va_end(ap);
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node); /* will also free children */
+       va_end(ap);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_subset_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = EC_NODE_SUBSET(EC_NO_ID,
+               EC_NODE_OR(EC_NO_ID,
+                       ec_node_str(EC_NO_ID, "foo"),
+                       ec_node_str(EC_NO_ID, "bar")),
+               ec_node_str(EC_NO_ID, "bar"),
+               ec_node_str(EC_NO_ID, "toto")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "titi");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "toto");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, " ");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "foox");
+       ec_node_free(node);
+
+       /* test completion */
+       node = EC_NODE_SUBSET(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_str(EC_NO_ID, "bar"),
+               ec_node_str(EC_NO_ID, "bar2"),
+               ec_node_str(EC_NO_ID, "toto"),
+               ec_node_str(EC_NO_ID, "titi")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "bar2", "bar", "foo", "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", "bar2", "", EC_NODE_ENDLIST,
+               "foo", "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "b", EC_NODE_ENDLIST,
+               "bar", "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", EC_NODE_ENDLIST,
+               "bar", "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", "b", EC_NODE_ENDLIST,
+               "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "t", EC_NODE_ENDLIST,
+               "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "to", EC_NODE_ENDLIST,
+               "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_subset_test = {
+       .name = "node_subset",
+       .test = ec_node_subset_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_subset_test);
diff --git a/libecoli/ecoli_node_subset.h b/libecoli/ecoli_node_subset.h
new file mode 100644 (file)
index 0000000..734b1ae
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_SUBSET_
+#define ECOLI_NODE_SUBSET_
+
+#include <ecoli_node.h>
+
+#define EC_NODE_SUBSET(args...) __ec_node_subset(args, EC_NODE_ENDLIST)
+
+/* list must be terminated with EC_NODE_ENDLIST */
+/* all nodes given in the list will be freed when freeing this one */
+/* avoid using this function directly, prefer the macro EC_NODE_SUBSET() or
+ * ec_node_subset() + ec_node_subset_add() */
+struct ec_node *__ec_node_subset(const char *id, ...);
+
+struct ec_node *ec_node_subset(const char *id);
+
+/* child is consumed */
+int ec_node_subset_add(struct ec_node *node, struct ec_node *child);
+
+#endif
diff --git a/libecoli/ecoli_parse.c b/libecoli/ecoli_parse.c
new file mode 100644 (file)
index 0000000..6396fc1
--- /dev/null
@@ -0,0 +1,540 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_assert.h>
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+#include <ecoli_node_sh_lex.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_parse.h>
+
+EC_LOG_TYPE_REGISTER(parse);
+
+TAILQ_HEAD(ec_parse_list, ec_parse);
+
+struct ec_parse {
+       TAILQ_ENTRY(ec_parse) next;
+       struct ec_parse_list children;
+       struct ec_parse *parent;
+       const struct ec_node *node;
+       struct ec_strvec *strvec;
+       struct ec_keyval *attrs;
+};
+
+static int __ec_node_parse_child(const struct ec_node *node,
+                               struct ec_parse *state,
+                               bool is_root, const struct ec_strvec *strvec)
+{
+       struct ec_strvec *match_strvec;
+       struct ec_parse *child = NULL;
+       int ret;
+
+       if (ec_node_type(node)->parse == NULL) {
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       if (!is_root) {
+               child = ec_parse(node);
+               if (child == NULL)
+                       return -1;
+
+               ec_parse_link_child(state, child);
+       } else {
+               child = state;
+       }
+       ret = ec_node_type(node)->parse(node, child, strvec);
+       if (ret < 0)
+               goto fail;
+
+       if (ret == EC_PARSE_NOMATCH) {
+               if (!is_root) {
+                       ec_parse_unlink_child(state, child);
+                       ec_parse_free(child);
+               }
+               return ret;
+       }
+
+       match_strvec = ec_strvec_ndup(strvec, 0, ret);
+       if (match_strvec == NULL)
+               goto fail;
+
+       child->strvec = match_strvec;
+
+       return ret;
+
+fail:
+       if (!is_root) {
+               ec_parse_unlink_child(state, child);
+               ec_parse_free(child);
+       }
+       return -1;
+}
+
+int ec_node_parse_child(const struct ec_node *node, struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       assert(state != NULL);
+       return __ec_node_parse_child(node, state, false, strvec);
+}
+
+struct ec_parse *ec_node_parse_strvec(const struct ec_node *node,
+                               const struct ec_strvec *strvec)
+{
+       struct ec_parse *parse = ec_parse(node);
+       int ret;
+
+       if (parse == NULL)
+               return NULL;
+
+       ret = __ec_node_parse_child(node, parse, true, strvec);
+       if (ret < 0) {
+               ec_parse_free(parse);
+               return NULL;
+       }
+
+       return parse;
+}
+
+struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str)
+{
+       struct ec_strvec *strvec = NULL;
+       struct ec_parse *parse = NULL;
+
+       errno = ENOMEM;
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       if (ec_strvec_add(strvec, str) < 0)
+               goto fail;
+
+       parse = ec_node_parse_strvec(node, strvec);
+       if (parse == NULL)
+               goto fail;
+
+       ec_strvec_free(strvec);
+       return parse;
+
+ fail:
+       ec_strvec_free(strvec);
+       ec_parse_free(parse);
+       return NULL;
+}
+
+struct ec_parse *ec_parse(const struct ec_node *node)
+{
+       struct ec_parse *parse = NULL;
+
+       parse = ec_calloc(1, sizeof(*parse));
+       if (parse == NULL)
+               goto fail;
+
+       TAILQ_INIT(&parse->children);
+
+       parse->node = node;
+       parse->attrs = ec_keyval();
+       if (parse->attrs == NULL)
+               goto fail;
+
+       return parse;
+
+ fail:
+       if (parse != NULL)
+               ec_keyval_free(parse->attrs);
+       ec_free(parse);
+
+       return NULL;
+}
+
+static struct ec_parse *
+__ec_parse_dup(const struct ec_parse *root, const struct ec_parse *ref,
+               struct ec_parse **new_ref)
+{
+       struct ec_parse *dup = NULL;
+       struct ec_parse *child, *dup_child;
+       struct ec_keyval *attrs = NULL;
+
+       if (root == NULL)
+               return NULL;
+
+       dup = ec_parse(root->node);
+       if (dup == NULL)
+               return NULL;
+
+       if (root == ref)
+               *new_ref = dup;
+
+       attrs = ec_keyval_dup(root->attrs);
+       if (attrs == NULL)
+               goto fail;
+       ec_keyval_free(dup->attrs);
+       dup->attrs = attrs;
+
+       if (root->strvec != NULL) {
+               dup->strvec = ec_strvec_dup(root->strvec);
+               if (dup->strvec == NULL)
+                       goto fail;
+       }
+
+       TAILQ_FOREACH(child, &root->children, next) {
+               dup_child = __ec_parse_dup(child, ref, new_ref);
+               if (dup_child == NULL)
+                       goto fail;
+               ec_parse_link_child(dup, dup_child);
+       }
+
+       return dup;
+
+fail:
+       ec_parse_free(dup);
+       return NULL;
+}
+
+struct ec_parse *ec_parse_dup(const struct ec_parse *parse)
+{
+       const struct ec_parse *root;
+       struct ec_parse *dup_root, *dup = NULL;
+
+       root = ec_parse_get_root(parse);
+       dup_root = __ec_parse_dup(root, parse, &dup);
+       if (dup_root == NULL)
+               return NULL;
+       assert(dup != NULL);
+
+       return dup;
+}
+
+void ec_parse_free_children(struct ec_parse *parse)
+{
+       struct ec_parse *child;
+
+       if (parse == NULL)
+               return;
+
+       while (!TAILQ_EMPTY(&parse->children)) {
+               child = TAILQ_FIRST(&parse->children);
+               TAILQ_REMOVE(&parse->children, child, next);
+               child->parent = NULL;
+               ec_parse_free(child);
+       }
+}
+
+void ec_parse_free(struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return;
+
+       ec_assert_print(parse->parent == NULL,
+                       "parent not NULL in ec_parse_free()");
+
+       ec_parse_free_children(parse);
+       ec_strvec_free(parse->strvec);
+       ec_keyval_free(parse->attrs);
+       ec_free(parse);
+}
+
+static void __ec_parse_dump(FILE *out,
+       const struct ec_parse *parse, size_t indent)
+{
+       struct ec_parse *child;
+       const struct ec_strvec *vec;
+       const char *id = "none", *typename = "none";
+
+       /* node can be null when parsing is incomplete */
+       if (parse->node != NULL) {
+               id = parse->node->id;
+               typename = ec_node_type(parse->node)->name;
+       }
+
+       fprintf(out, "%*s" "type=%s id=%s vec=",
+               (int)indent * 4, "", typename, id);
+       vec = ec_parse_strvec(parse);
+       ec_strvec_dump(out, vec);
+
+       TAILQ_FOREACH(child, &parse->children, next)
+               __ec_parse_dump(out, child, indent + 1);
+}
+
+void ec_parse_dump(FILE *out, const struct ec_parse *parse)
+{
+       fprintf(out, "------------------- parse dump:\n");
+
+       if (parse == NULL) {
+               fprintf(out, "parse is NULL\n");
+               return;
+       }
+
+       /* only exist if it does not match (strvec == NULL) and if it
+        * does not have children: an incomplete parse, like those
+        * generated by complete() don't match but have children that
+        * may match. */
+       if (!ec_parse_matches(parse) && TAILQ_EMPTY(&parse->children)) {
+               fprintf(out, "no match\n");
+               return;
+       }
+
+       __ec_parse_dump(out, parse, 0);
+}
+
+void ec_parse_link_child(struct ec_parse *parse,
+       struct ec_parse *child)
+{
+       TAILQ_INSERT_TAIL(&parse->children, child, next);
+       child->parent = parse;
+}
+
+void ec_parse_unlink_child(struct ec_parse *parse,
+       struct ec_parse *child)
+{
+       TAILQ_REMOVE(&parse->children, child, next);
+       child->parent = NULL;
+}
+
+struct ec_parse *
+ec_parse_get_first_child(const struct ec_parse *parse)
+{
+       return TAILQ_FIRST(&parse->children);
+}
+
+struct ec_parse *
+ec_parse_get_last_child(const struct ec_parse *parse)
+{
+       return TAILQ_LAST(&parse->children, ec_parse_list);
+}
+
+struct ec_parse *ec_parse_get_next(const struct ec_parse *parse)
+{
+       return TAILQ_NEXT(parse, next);
+}
+
+bool ec_parse_has_child(const struct ec_parse *parse)
+{
+       return !TAILQ_EMPTY(&parse->children);
+}
+
+const struct ec_node *ec_parse_get_node(const struct ec_parse *parse)
+{
+       return parse->node;
+}
+
+void ec_parse_del_last_child(struct ec_parse *parse)
+{
+       struct ec_parse *child;
+
+       child = ec_parse_get_last_child(parse);
+       ec_parse_unlink_child(parse, child);
+       ec_parse_free(child);
+}
+
+struct ec_parse *__ec_parse_get_root(struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return NULL;
+
+       while (parse->parent != NULL)
+               parse = parse->parent;
+
+       return parse;
+}
+
+struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return NULL;
+
+       return parse->parent;
+}
+
+struct ec_parse *ec_parse_iter_next(struct ec_parse *parse)
+{
+       struct ec_parse *child, *parent, *next;
+
+       child = TAILQ_FIRST(&parse->children);
+       if (child != NULL)
+               return child;
+       parent = parse->parent;
+       while (parent != NULL) {
+               next = TAILQ_NEXT(parse, next);
+               if (next != NULL)
+                       return next;
+               parse = parent;
+               parent = parse->parent;
+       }
+       return NULL;
+}
+
+struct ec_parse *ec_parse_find_first(struct ec_parse *parse,
+       const char *id)
+{
+       struct ec_parse *iter;
+
+       if (parse == NULL)
+               return NULL;
+
+       for (iter = parse; iter != NULL; iter = ec_parse_iter_next(iter)) {
+               if (iter->node != NULL &&
+                               iter->node->id != NULL &&
+                               !strcmp(iter->node->id, id))
+                       return iter;
+       }
+
+       return NULL;
+}
+
+struct ec_keyval *
+ec_parse_get_attrs(struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return NULL;
+
+       return parse->attrs;
+}
+
+const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse)
+{
+       if (parse == NULL || parse->strvec == NULL)
+               return NULL;
+
+       return parse->strvec;
+}
+
+/* number of strings in the parse vector */
+size_t ec_parse_len(const struct ec_parse *parse)
+{
+       if (parse == NULL || parse->strvec == NULL)
+               return 0;
+
+       return ec_strvec_len(parse->strvec);
+}
+
+size_t ec_parse_matches(const struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return 0;
+
+       if (parse->strvec == NULL)
+               return 0;
+
+       return 1;
+}
+
+/* LCOV_EXCL_START */
+static int ec_parse_testcase(void)
+{
+       struct ec_node *node = NULL;
+       struct ec_parse *p = NULL, *p2 = NULL;
+       const struct ec_parse *pc;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+       int testres = 0;
+       int ret;
+
+       node = ec_node_sh_lex(EC_NO_ID,
+                       EC_NODE_SEQ(EC_NO_ID,
+                               ec_node_str("id_x", "x"),
+                               ec_node_str("id_y", "y")));
+       if (node == NULL)
+               goto fail;
+
+       p = ec_node_parse(node, "xcdscds");
+       testres |= EC_TEST_CHECK(
+               p != NULL && !ec_parse_matches(p),
+               "parse should not match\n");
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_parse_dump(f, p);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "no match"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+       ec_parse_free(p);
+
+       p = ec_node_parse(node, "x y");
+       testres |= EC_TEST_CHECK(
+               p != NULL && ec_parse_matches(p),
+               "parse should match\n");
+       testres |= EC_TEST_CHECK(
+               ec_parse_len(p) == 1, "bad parse len\n");
+
+       ret = ec_keyval_set(ec_parse_get_attrs(p), "key", "val", NULL);
+       testres |= EC_TEST_CHECK(ret == 0,
+               "cannot set parse attribute\n");
+
+       p2 = ec_parse_dup(p);
+       testres |= EC_TEST_CHECK(
+               p2 != NULL && ec_parse_matches(p2),
+               "parse should match\n");
+       ec_parse_free(p2);
+       p2 = NULL;
+
+       pc = ec_parse_find_first(p, "id_x");
+       testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_x");
+       testres |= EC_TEST_CHECK(pc != NULL &&
+               ec_parse_get_parent(pc) != NULL &&
+               ec_parse_get_parent(ec_parse_get_parent(pc)) == p,
+               "invalid parent\n");
+
+       pc = ec_parse_find_first(p, "id_y");
+       testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_y");
+       pc = ec_parse_find_first(p, "id_dezdezdez");
+       testres |= EC_TEST_CHECK(pc == NULL, "should not find bad id");
+
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_parse_dump(f, p);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "type=sh_lex id=no-id") &&
+               strstr(buf, "type=seq id=no-id") &&
+               strstr(buf, "type=str id=id_x") &&
+               strstr(buf, "type=str id=id_x"),
+               "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       ec_parse_free(p);
+       ec_node_free(node);
+       return testres;
+
+fail:
+       ec_parse_free(p2);
+       ec_parse_free(p);
+       ec_node_free(node);
+       if (f != NULL)
+               fclose(f);
+       free(buf);
+
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_parse_test = {
+       .name = "parse",
+       .test = ec_parse_testcase,
+};
+
+EC_TEST_REGISTER(ec_parse_test);
diff --git a/libecoli/ecoli_parse.h b/libecoli/ecoli_parse.h
new file mode 100644 (file)
index 0000000..79e644f
--- /dev/null
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Node parse API.
+ *
+ * The parse operation is to check if an input (a string or vector of
+ * strings) matches the node tree. On success, the result is stored in a
+ * tree that describes which part of the input matches which node.
+ */
+
+#ifndef ECOLI_PARSE_
+#define ECOLI_PARSE_
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <stdio.h>
+
+struct ec_node;
+struct ec_parse;
+
+/**
+ * Create an empty parse tree.
+ *
+ * @return
+ *   The empty parse tree.
+ */
+struct ec_parse *ec_parse(const struct ec_node *node);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_free(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_free_children(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_dup(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse);
+
+/* a NULL return value is an error, with errno set
+  ENOTSUP: no ->parse() operation
+*/
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_node_parse_strvec(const struct ec_node *node,
+                               const struct ec_strvec *strvec);
+
+/**
+ *
+ *
+ *
+ */
+#define EC_PARSE_NOMATCH INT_MAX
+
+/* internal: used by nodes
+ *
+ * state is the current parse tree, which is built piece by piece while
+ *   parsing the node tree: ec_node_parse_child() creates a new child in
+ *   this state parse tree, and calls the parse() method for the child
+ *   node, with state pointing to this new child. If it does not match,
+ *   the child is removed in the state, else it is kept, with its
+ *   possible descendants.
+ *
+ * return:
+ * the number of matched strings in strvec on success
+ * EC_PARSE_NOMATCH (positive) if it does not match
+ * -1 on error, and errno is set
+ */
+int ec_node_parse_child(const struct ec_node *node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_link_child(struct ec_parse *parse,
+                       struct ec_parse *child);
+/**
+ *
+ *
+ *
+ */
+void ec_parse_unlink_child(struct ec_parse *parse,
+                       struct ec_parse *child);
+
+/* keep the const */
+#define ec_parse_get_root(parse) ({                            \
+       const struct ec_parse *p_ = parse; /* check type */     \
+       struct ec_parse *parse_ = (struct ec_parse *)parse;     \
+       typeof(parse) res_;                                     \
+       (void)p_;                                               \
+       res_ = __ec_parse_get_root(parse_);                     \
+       res_;                                                   \
+})
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *__ec_parse_get_root(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse);
+
+/**
+ * Get the first child of a tree.
+ *
+ */
+struct ec_parse *ec_parse_get_first_child(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_get_last_child(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_get_next(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+#define EC_PARSE_FOREACH_CHILD(child, parse)                   \
+       for (child = ec_parse_get_first_child(parse);           \
+               child != NULL;                                  \
+               child = ec_parse_get_next(child))               \
+
+/**
+ *
+ *
+ *
+ */
+bool ec_parse_has_child(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+const struct ec_node *ec_parse_get_node(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_del_last_child(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_keyval *ec_parse_get_attrs(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_dump(FILE *out, const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_find_first(struct ec_parse *parse,
+       const char *id);
+
+/**
+ * Iterate among parse tree
+ *
+ * Use it with:
+ * for (iter = state; iter != NULL; iter = ec_parse_iter_next(iter))
+ */
+struct ec_parse *ec_parse_iter_next(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+size_t ec_parse_len(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+size_t ec_parse_matches(const struct ec_parse *parse);
+
+#endif
diff --git a/libecoli/ecoli_string.c b/libecoli/ecoli_string.c
new file mode 100644 (file)
index 0000000..7723818
--- /dev/null
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <ecoli_assert.h>
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+
+/* count the number of identical chars at the beginning of 2 strings */
+size_t ec_strcmp_count(const char *s1, const char *s2)
+{
+       size_t i = 0;
+
+       while (s1[i] && s2[i] && s1[i] == s2[i])
+               i++;
+
+       return i;
+}
+
+int ec_str_startswith(const char *s, const char *beginning)
+{
+       size_t len;
+
+       len = ec_strcmp_count(s, beginning);
+       if (beginning[len] == '\0')
+               return 1;
+
+       return 0;
+}
+
+int ec_vasprintf(char **buf, const char *fmt, va_list ap)
+{
+       char dummy;
+       int buflen, ret;
+       va_list aq;
+
+       va_copy(aq, ap);
+       *buf = NULL;
+       ret = vsnprintf(&dummy, 1, fmt, aq);
+       va_end(aq);
+       if (ret < 0)
+               return ret;
+
+       buflen = ret + 1;
+       *buf = ec_malloc(buflen);
+       if (*buf == NULL)
+               return -1;
+
+       va_copy(aq, ap);
+       ret = vsnprintf(*buf, buflen, fmt, aq);
+       va_end(aq);
+
+       ec_assert_print(ret < buflen, "invalid return value for vsnprintf");
+       if (ret < 0) {
+               free(*buf);
+               *buf = NULL;
+               return -1;
+       }
+
+       return ret;
+}
+
+int ec_asprintf(char **buf, const char *fmt, ...)
+{
+       va_list ap;
+       int ret;
+
+       va_start(ap, fmt);
+       ret = ec_vasprintf(buf, fmt, ap);
+       va_end(ap);
+
+       return ret;
+}
diff --git a/libecoli/ecoli_string.h b/libecoli/ecoli_string.h
new file mode 100644 (file)
index 0000000..add73a0
--- /dev/null
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_STRING_
+#define ECOLI_STRING_
+
+#include <stddef.h>
+
+/* count the number of identical chars at the beginning of 2 strings */
+size_t ec_strcmp_count(const char *s1, const char *s2);
+
+/* return 1 if 's' starts with 'beginning' */
+int ec_str_startswith(const char *s, const char *beginning);
+
+/* like asprintf, but use libecoli allocator */
+int ec_asprintf(char **buf, const char *fmt, ...);
+
+/* like vasprintf, but use libecoli allocator */
+int ec_vasprintf(char **buf, const char *fmt, va_list ap);
+
+#endif
diff --git a/libecoli/ecoli_strvec.c b/libecoli/ecoli_strvec.c
new file mode 100644 (file)
index 0000000..c574b1b
--- /dev/null
@@ -0,0 +1,427 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#define _GNU_SOURCE /* qsort_r */
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_log.h>
+#include <ecoli_strvec.h>
+
+EC_LOG_TYPE_REGISTER(strvec);
+
+struct ec_strvec_elt {
+       unsigned int refcnt;
+       char *str;
+};
+
+struct ec_strvec {
+       size_t len;
+       struct ec_strvec_elt **vec;
+};
+
+struct ec_strvec *ec_strvec(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)
+{
+       struct ec_strvec_elt *elt, **new_vec;
+
+       new_vec = ec_realloc(strvec->vec,
+               sizeof(*strvec->vec) * (strvec->len + 1));
+       if (new_vec == NULL)
+               return -1;
+
+       strvec->vec = new_vec;
+
+       elt = ec_malloc(sizeof(*elt));
+       if (elt == NULL)
+               return -1;
+
+       elt->str = ec_strdup(s);
+       if (elt->str == NULL) {
+               ec_free(elt);
+               return -1;
+       }
+       elt->refcnt = 1;
+
+       new_vec[strvec->len] = elt;
+       strvec->len++;
+       return 0;
+}
+
+struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
+       size_t n)
+{
+       struct ec_strvec *strvec = NULL;
+       size_t i;
+
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       for (i = 0; i < n; i++) {
+               if (ec_strvec_add(strvec, strarr[i]) < 0)
+                       goto fail;
+       }
+
+       return strvec;
+
+fail:
+       ec_strvec_free(strvec);
+       return NULL;
+}
+
+int ec_strvec_del_last(struct ec_strvec *strvec)
+{
+       struct ec_strvec_elt *elt;
+
+       if (strvec->len == 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       elt = strvec->vec[strvec->len - 1];
+       elt->refcnt--;
+       if (elt->refcnt == 0) {
+               ec_free(elt->str);
+               ec_free(elt);
+       }
+       strvec->len--;
+       return 0;
+}
+
+struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t off,
+       size_t len)
+{
+       struct ec_strvec *copy = NULL;
+       size_t i, veclen;
+
+       veclen = ec_strvec_len(strvec);
+       if (off + len > veclen)
+               return NULL;
+
+       copy = ec_strvec();
+       if (copy == NULL)
+               goto fail;
+
+       if (len == 0)
+               return copy;
+
+       copy->vec = ec_calloc(len, sizeof(*copy->vec));
+       if (copy->vec == NULL)
+               goto fail;
+
+       for (i = 0; i < len; i++) {
+               copy->vec[i] = strvec->vec[i + off];
+               copy->vec[i]->refcnt++;
+       }
+       copy->len = 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, 0, ec_strvec_len(strvec));
+}
+
+void ec_strvec_free(struct ec_strvec *strvec)
+{
+       struct ec_strvec_elt *elt;
+       size_t i;
+
+       if (strvec == NULL)
+               return;
+
+       for (i = 0; i < ec_strvec_len(strvec); i++) {
+               elt = strvec->vec[i];
+               elt->refcnt--;
+               if (elt->refcnt == 0) {
+                       ec_free(elt->str);
+                       ec_free(elt);
+               }
+       }
+
+       ec_free(strvec->vec);
+       ec_free(strvec);
+}
+
+size_t ec_strvec_len(const struct ec_strvec *strvec)
+{
+       return strvec->len;
+}
+
+const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx)
+{
+       if (strvec == NULL || idx >= strvec->len)
+               return NULL;
+
+       return strvec->vec[idx]->str;
+}
+
+int ec_strvec_cmp(const struct ec_strvec *strvec1,
+               const struct ec_strvec *strvec2)
+{
+       size_t i;
+
+       if (ec_strvec_len(strvec1) != ec_strvec_len(strvec2))
+               return -1;
+
+       for (i = 0; i < ec_strvec_len(strvec1); i++) {
+               if (strcmp(ec_strvec_val(strvec1, i),
+                               ec_strvec_val(strvec2, i)))
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int
+cmp_vec_elt(const void *p1, const void *p2, void *arg)
+{
+       int (*str_cmp)(const char *s1, const char *s2) = arg;
+       const struct ec_strvec_elt * const *e1 = p1, * const *e2 = p2;
+
+       return str_cmp((*e1)->str, (*e2)->str);
+}
+
+void ec_strvec_sort(struct ec_strvec *strvec,
+       int (*str_cmp)(const char *s1, const char *s2))
+{
+       if (str_cmp == NULL)
+               str_cmp = strcmp;
+       qsort_r(strvec->vec, ec_strvec_len(strvec),
+               sizeof(*strvec->vec), cmp_vec_elt, str_cmp);
+}
+
+void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec)
+{
+       size_t i;
+
+       if (strvec == NULL) {
+               fprintf(out, "none\n");
+               return;
+       }
+
+       fprintf(out, "strvec (len=%zu) [", strvec->len);
+       for (i = 0; i < ec_strvec_len(strvec); i++) {
+               if (i == 0)
+                       fprintf(out, "%s", strvec->vec[i]->str);
+               else
+                       fprintf(out, ", %s", strvec->vec[i]->str);
+       }
+       fprintf(out, "]\n");
+
+}
+
+/* LCOV_EXCL_START */
+static int ec_strvec_testcase(void)
+{
+       struct ec_strvec *strvec = NULL;
+       struct ec_strvec *strvec2 = NULL;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+       int testres = 0;
+
+       strvec = ec_strvec();
+       if (strvec == NULL) {
+               EC_TEST_ERR("cannot create strvec\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec) != 0) {
+               EC_TEST_ERR("bad strvec len (0)\n");
+               goto fail;
+       }
+       if (ec_strvec_add(strvec, "0") < 0) {
+               EC_TEST_ERR("cannot add (0) in strvec\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec) != 1) {
+               EC_TEST_ERR("bad strvec len (1)\n");
+               goto fail;
+       }
+       if (ec_strvec_add(strvec, "1") < 0) {
+               EC_TEST_ERR("cannot add (1) in strvec\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec) != 2) {
+               EC_TEST_ERR("bad strvec len (2)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec, 0), "0")) {
+               EC_TEST_ERR("invalid element in strvec (0)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec, 1), "1")) {
+               EC_TEST_ERR("invalid element in strvec (1)\n");
+               goto fail;
+       }
+       if (ec_strvec_val(strvec, 2) != NULL) {
+               EC_TEST_ERR("strvec val should be NULL\n");
+               goto fail;
+       }
+
+       strvec2 = ec_strvec_dup(strvec);
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec2\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec2) != 2) {
+               EC_TEST_ERR("bad strvec2 len (2)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec2, 0), "0")) {
+               EC_TEST_ERR("invalid element in strvec2 (0)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec2, 1), "1")) {
+               EC_TEST_ERR("invalid element in strvec2 (1)\n");
+               goto fail;
+       }
+       if (ec_strvec_val(strvec2, 2) != NULL) {
+               EC_TEST_ERR("strvec2 val should be NULL\n");
+               goto fail;
+       }
+       ec_strvec_free(strvec2);
+
+       strvec2 = ec_strvec_ndup(strvec, 0, 0);
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec2\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec2) != 0) {
+               EC_TEST_ERR("bad strvec2 len (0)\n");
+               goto fail;
+       }
+       if (ec_strvec_val(strvec2, 0) != NULL) {
+               EC_TEST_ERR("strvec2 val should be NULL\n");
+               goto fail;
+       }
+       ec_strvec_free(strvec2);
+
+       strvec2 = ec_strvec_ndup(strvec, 1, 1);
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec2\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec2) != 1) {
+               EC_TEST_ERR("bad strvec2 len (1)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec2, 0), "1")) {
+               EC_TEST_ERR("invalid element in strvec2 (1)\n");
+               goto fail;
+       }
+       if (ec_strvec_val(strvec2, 1) != NULL) {
+               EC_TEST_ERR("strvec2 val should be NULL\n");
+               goto fail;
+       }
+       ec_strvec_free(strvec2);
+
+       strvec2 = ec_strvec_ndup(strvec, 3, 1);
+       if (strvec2 != NULL) {
+               EC_TEST_ERR("strvec2 should be NULL\n");
+               goto fail;
+       }
+       ec_strvec_free(strvec2);
+
+       strvec2 = EC_STRVEC("0", "1");
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec from array\n");
+               goto fail;
+       }
+       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
+               "strvec and strvec2 should be equal\n");
+       ec_strvec_free(strvec2);
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_strvec_dump(f, strvec);
+       fclose(f);
+       f = NULL;
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "strvec (len=2) [0, 1]"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       ec_strvec_del_last(strvec);
+       strvec2 = EC_STRVEC("0");
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec from array\n");
+               goto fail;
+       }
+       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
+               "strvec and strvec2 should be equal\n");
+       ec_strvec_free(strvec2);
+       strvec2 = NULL;
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_strvec_dump(f, NULL);
+       fclose(f);
+       f = NULL;
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "none"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       ec_strvec_free(strvec);
+
+       strvec = EC_STRVEC("e", "a", "f", "d", "b", "c");
+       if (strvec == NULL) {
+               EC_TEST_ERR("cannot create strvec from array\n");
+               goto fail;
+       }
+       ec_strvec_sort(strvec, NULL);
+       strvec2 = EC_STRVEC("a", "b", "c", "d", "e", "f");
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec from array\n");
+               goto fail;
+       }
+       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
+               "strvec and strvec2 should be equal\n");
+       ec_strvec_free(strvec);
+       strvec = NULL;
+       ec_strvec_free(strvec2);
+       strvec2 = NULL;
+
+       return testres;
+
+fail:
+       if (f != NULL)
+               fclose(f);
+       ec_strvec_free(strvec);
+       ec_strvec_free(strvec2);
+       free(buf);
+
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_str_test = {
+       .name = "strvec",
+       .test = ec_strvec_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_str_test);
diff --git a/libecoli/ecoli_strvec.h b/libecoli/ecoli_strvec.h
new file mode 100644 (file)
index 0000000..8e14973
--- /dev/null
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Vectors of strings.
+ *
+ * The ec_strvec API provide helpers to manipulate string vectors.
+ * When duplicating vectors, the strings are not duplicated in memory,
+ * a reference counter is used.
+ */
+
+#ifndef ECOLI_STRVEC_
+#define ECOLI_STRVEC_
+
+#include <stdio.h>
+
+/**
+ * Allocate a new empty string vector.
+ *
+ * @return
+ *   The new strvec object, or NULL on error (errno is set).
+ */
+struct ec_strvec *ec_strvec(void);
+
+#ifndef EC_COUNT_OF
+#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
+               ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+#endif
+
+/**
+ * Allocate a new string vector
+ *
+ * The string vector is initialized with the list of const strings
+ * passed as arguments.
+ *
+ * @return
+ *   The new strvec object, or NULL on error (errno is set).
+ */
+#define EC_STRVEC(args...) ({                                          \
+                       const char *_arr[] = {args};                    \
+                       ec_strvec_from_array(_arr, EC_COUNT_OF(_arr));  \
+               })
+/**
+ * Allocate a new string vector
+ *
+ * The string vector is initialized with the array of const strings
+ * passed as arguments.
+ *
+ * @param strarr
+ *   The array of const strings.
+ * @param n
+ *   The number of strings in the array.
+ * @return
+ *   The new strvec object, or NULL on error (errno is set).
+ */
+struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
+       size_t n);
+
+/**
+ * Add a string in a vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param s
+ *   The string to be added at the end of the vector.
+ * @return
+ *   0 on success or -1 on error (errno is set).
+ */
+int ec_strvec_add(struct ec_strvec *strvec, const char *s);
+
+/**
+ * Delete the last entry in the string vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param s
+ *   The string to be added at the end of the vector.
+ * @return
+ *   0 on success or -1 on error (errno is set).
+ */
+int ec_strvec_del_last(struct ec_strvec *strvec);
+
+/**
+ * Duplicate a string vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @return
+ *   The duplicated strvec object, or NULL on error (errno is set).
+ */
+struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec);
+
+/**
+ * Duplicate a part of a string vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param off
+ *   The index of the first string to duplicate.
+ * @param
+ *   The number of strings to duplicate.
+ * @return
+ *   The duplicated strvec object, or NULL on error (errno is set).
+ */
+struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec,
+       size_t off, size_t len);
+
+/**
+ * Free a string vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ */
+void ec_strvec_free(struct ec_strvec *strvec);
+
+/**
+ * Get the length of a string vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @return
+ *   The length of the vector.
+ */
+size_t ec_strvec_len(const struct ec_strvec *strvec);
+
+/**
+ * Get a string element from a vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param idx
+ *   The index of the string to get.
+ * @return
+ *   The string stored at given index, or NULL on error (strvec is NULL
+ *   or invalid index).
+ */
+const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx);
+
+/**
+ * Compare two string vectors
+ *
+ * @param strvec
+ *   The pointer to the first string vector.
+ * @param strvec
+ *   The pointer to the second string vector.
+ * @return
+ *   0 if the string vectors are equal.
+ */
+int ec_strvec_cmp(const struct ec_strvec *strvec1,
+               const struct ec_strvec *strvec2);
+
+/**
+ * Sort the string vector.
+ *
+ * @param strvec
+ *   The pointer to the first string vector.
+ * @param str_cmp
+ *   The sort function to use. If NULL, use strcmp.
+ */
+void ec_strvec_sort(struct ec_strvec *strvec,
+               int (*str_cmp)(const char *s1, const char *s2));
+
+/**
+ * Dump a string vector.
+ *
+ * @param out
+ *   The stream where the dump is sent.
+ * @param strvec
+ *   The pointer to the string vector.
+ */
+void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec);
+
+#endif
diff --git a/libecoli/ecoli_test.c b/libecoli/ecoli_test.c
new file mode 100644 (file)
index 0000000..b090bd3
--- /dev/null
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_parse.h>
+
+static struct ec_test_list test_list = TAILQ_HEAD_INITIALIZER(test_list);
+
+EC_LOG_TYPE_REGISTER(test);
+
+static struct ec_test *ec_test_lookup(const char *name)
+{
+       struct ec_test *test;
+
+       TAILQ_FOREACH(test, &test_list, next) {
+               if (!strcmp(name, test->name))
+                       return test;
+       }
+
+       errno = EEXIST;
+       return NULL;
+}
+
+int ec_test_register(struct ec_test *test)
+{
+       if (ec_test_lookup(test->name) != NULL)
+               return -1;
+
+       TAILQ_INSERT_TAIL(&test_list, test, next);
+
+       return 0;
+}
+
+int ec_test_check_parse(struct ec_node *tk, int expected, ...)
+{
+       struct ec_parse *p;
+       struct ec_strvec *vec = NULL;
+       const char *s;
+       int ret = -1, match;
+       va_list ap;
+
+       va_start(ap, expected);
+
+       /* build a string vector */
+       vec = ec_strvec();
+       if (vec == NULL)
+               goto out;
+
+       for (s = va_arg(ap, const char *);
+            s != EC_NODE_ENDLIST;
+            s = va_arg(ap, const char *)) {
+               if (s == NULL)
+                       goto out;
+
+               if (ec_strvec_add(vec, s) < 0)
+                       goto out;
+       }
+
+       p = ec_node_parse_strvec(tk, vec);
+       if (p == NULL) {
+               EC_LOG(EC_LOG_ERR, "parse is NULL\n");
+       }
+       if (ec_parse_matches(p))
+               match = ec_parse_len(p);
+       else
+               match = -1;
+       if (expected == match) {
+               ret = 0;
+       } else {
+               EC_LOG(EC_LOG_ERR,
+                       "parse len (%d) does not match expected (%d)\n",
+                       match, expected);
+       }
+
+       ec_parse_free(p);
+
+out:
+       ec_strvec_free(vec);
+       va_end(ap);
+       return ret;
+}
+
+int ec_test_check_complete(struct ec_node *tk, enum ec_comp_type type, ...)
+{
+       struct ec_comp *c = NULL;
+       struct ec_strvec *vec = NULL;
+       const char *s;
+       int ret = 0;
+       unsigned int count = 0;
+       va_list ap;
+
+       va_start(ap, type);
+
+       /* build a string vector */
+       vec = ec_strvec();
+       if (vec == NULL)
+               goto out;
+
+       for (s = va_arg(ap, const char *);
+            s != EC_NODE_ENDLIST;
+            s = va_arg(ap, const char *)) {
+               if (s == NULL)
+                       goto out;
+
+               if (ec_strvec_add(vec, s) < 0)
+                       goto out;
+       }
+
+       c = ec_node_complete_strvec(tk, vec);
+       if (c == NULL) {
+               ret = -1;
+               goto out;
+       }
+
+       /* for each expected completion, check it is there */
+       for (s = va_arg(ap, const char *);
+            s != EC_NODE_ENDLIST;
+            s = va_arg(ap, const char *)) {
+               struct ec_comp_iter *iter;
+               const struct ec_comp_item *item;
+
+               if (s == NULL) {
+                       ret = -1;
+                       goto out;
+               }
+
+               count++;
+
+               /* only check matching completions */
+               iter = ec_comp_iter(c, type);
+               while ((item = ec_comp_iter_next(iter)) != NULL) {
+                       const char *str = ec_comp_item_get_str(item);
+                       if (str != NULL && strcmp(str, s) == 0)
+                               break;
+               }
+
+               if (item == NULL) {
+                       EC_LOG(EC_LOG_ERR,
+                               "completion <%s> not in list\n", s);
+                       ret = -1;
+               }
+               ec_comp_iter_free(iter);
+       }
+
+       /* check if we have more completions (or less) than expected */
+       if (count != ec_comp_count(c, type)) {
+               EC_LOG(EC_LOG_ERR,
+                       "nb_completion (%d) does not match (%d)\n",
+                       count, ec_comp_count(c, type));
+               ec_comp_dump(stdout, c);
+               ret = -1;
+       }
+
+out:
+       ec_strvec_free(vec);
+       ec_comp_free(c);
+       va_end(ap);
+       return ret;
+}
+
+static int launch_test(const char *name)
+{
+       struct ec_test *test;
+       int ret = 0;
+       unsigned int count = 0;
+
+       TAILQ_FOREACH(test, &test_list, next) {
+               if (name != NULL && strcmp(name, test->name))
+                       continue;
+
+               EC_LOG(EC_LOG_INFO, "== starting test %-20s\n",
+                       test->name);
+
+               count++;
+               if (test->test() == 0) {
+                       EC_LOG(EC_LOG_INFO,
+                               "== test %-20s success\n",
+                               test->name);
+               } else {
+                       EC_LOG(EC_LOG_INFO,
+                               "== test %-20s failed\n",
+                               test->name);
+                       ret = -1;
+               }
+       }
+
+       if (name != NULL && count == 0) {
+               EC_LOG(EC_LOG_WARNING,
+                       "== test %s not found\n", name);
+               ret = -1;
+       }
+
+       return ret;
+}
+
+int ec_test_all(void)
+{
+       return launch_test(NULL);
+}
+
+int ec_test_one(const char *name)
+{
+       return launch_test(name);
+}
diff --git a/libecoli/ecoli_test.h b/libecoli/ecoli_test.h
new file mode 100644 (file)
index 0000000..94cd0b9
--- /dev/null
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_TEST_
+#define ECOLI_TEST_
+
+#include <sys/queue.h>
+
+#include <ecoli_log.h>
+
+struct ec_node;
+enum ec_comp_type;
+
+#define EC_TEST_REGISTER(t)                                            \
+       static void ec_test_init_##t(void);                             \
+       static void __attribute__((constructor, used))                  \
+       ec_test_init_##t(void)                                          \
+       {                                                               \
+               if (ec_test_register(&t) < 0)                           \
+                       fprintf(stderr, "cannot register test %s\n",    \
+                               t.name);                                \
+       }
+
+/**
+ * Type of test function. Return 0 on success, -1 on error.
+ */
+typedef int (ec_test_t)(void);
+
+TAILQ_HEAD(ec_test_list, ec_test);
+
+/**
+ * A structure describing a test case.
+ */
+struct ec_test {
+       TAILQ_ENTRY(ec_test) next;  /**< Next in list. */
+       const char *name;           /**< Test name. */
+       ec_test_t *test;            /**< Test function. */
+};
+
+/**
+ * Register a test case.
+ *
+ * @param test
+ *   A pointer to a ec_test structure describing the test
+ *   to be registered.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_test_register(struct ec_test *test);
+
+int ec_test_all(void);
+int ec_test_one(const char *name);
+
+/* expected == -1 means no match */
+int ec_test_check_parse(struct ec_node *node, int expected, ...);
+
+#define EC_TEST_ERR(fmt, ...)                                          \
+       EC_LOG(EC_LOG_ERR, "%s:%d: error: " fmt "\n",                   \
+               __FILE__, __LINE__, ##__VA_ARGS__);                     \
+
+#define EC_TEST_CHECK(cond, fmt, ...) ({                               \
+       int ret_ = 0;                                                   \
+       if (!(cond)) {                                                  \
+               EC_TEST_ERR("(" #cond ") is wrong. " fmt                \
+                       ##__VA_ARGS__);                                 \
+               ret_ = -1;                                              \
+       }                                                               \
+       ret_;                                                           \
+})
+
+/* node, input, [expected1, expected2, ...] */
+#define EC_TEST_CHECK_PARSE(node, args...) ({                          \
+       int ret_ = ec_test_check_parse(node, args, EC_NODE_ENDLIST);    \
+       if (ret_)                                                       \
+               EC_TEST_ERR("parse test failed");                       \
+       ret_;                                                           \
+})
+
+int ec_test_check_complete(struct ec_node *node,
+                       enum ec_comp_type type, ...);
+
+#define EC_TEST_CHECK_COMPLETE(node, args...) ({                       \
+       int ret_ = ec_test_check_complete(node, EC_COMP_FULL, args);    \
+       if (ret_)                                                       \
+               EC_TEST_ERR("complete test failed");                    \
+       ret_;                                                           \
+})
+
+#define EC_TEST_CHECK_COMPLETE_PARTIAL(node, args...) ({               \
+       int ret_ = ec_test_check_complete(node, EC_COMP_PARTIAL, args); \
+       if (ret_)                                                       \
+               EC_TEST_ERR("complete test failed");                    \
+       ret_;                                                           \
+})
+
+#endif
diff --git a/libecoli/ecoli_vec.c b/libecoli/ecoli_vec.c
new file mode 100644 (file)
index 0000000..fe8c572
--- /dev/null
@@ -0,0 +1,468 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <ecoli_assert.h>
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_vec.h>
+
+EC_LOG_TYPE_REGISTER(vec);
+
+struct ec_vec {
+       size_t len;
+       size_t size;
+       size_t elt_size;
+       ec_vec_elt_copy_t copy;
+       ec_vec_elt_free_t free;
+       void *vec;
+};
+
+static void *get_obj(const struct ec_vec *vec, size_t idx)
+{
+       assert(vec->elt_size != 0);
+       return (char *)vec->vec + (idx * vec->elt_size);
+}
+
+struct ec_vec *
+ec_vec(size_t elt_size, size_t size, ec_vec_elt_copy_t copy,
+       ec_vec_elt_free_t free)
+{
+       struct ec_vec *vec;
+
+       if (elt_size == 0) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       vec = ec_calloc(1, sizeof(*vec));
+       if (vec == NULL)
+               return NULL;
+
+       vec->elt_size = elt_size;
+       vec->copy = copy;
+       vec->free = free;
+
+       if (size == 0)
+               return vec;
+
+       vec->vec = ec_calloc(size, vec->elt_size);
+       if (vec->vec == NULL) {
+               ec_free(vec);
+               return NULL;
+       }
+
+       return vec;
+}
+
+int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr)
+{
+       void *new_vec;
+
+       if (vec->len + 1 > vec->size) {
+               new_vec = ec_realloc(vec->vec, vec->elt_size * (vec->len + 1));
+               if (new_vec == NULL)
+                       return -1;
+               vec->size = vec->len + 1;
+               vec->vec = new_vec;
+       }
+
+       memcpy(get_obj(vec, vec->len), ptr, vec->elt_size);
+       vec->len++;
+
+       return 0;
+}
+
+int ec_vec_add_ptr(struct ec_vec *vec, void *elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+struct ec_vec *ec_vec_ndup(const struct ec_vec *vec, size_t off,
+       size_t len)
+{
+       struct ec_vec *copy = NULL;
+       size_t i, veclen;
+
+       veclen = ec_vec_len(vec);
+       if (off + len > veclen)
+               return NULL;
+
+       copy = ec_vec(vec->elt_size, len, vec->copy, vec->free);
+       if (copy == NULL)
+               goto fail;
+
+       if (len == 0)
+               return copy;
+
+       for (i = 0; i < len; i++) {
+               if (vec->copy)
+                       vec->copy(get_obj(copy, i), get_obj(vec, i + off));
+               else
+                       memcpy(get_obj(copy, i), get_obj(vec, i + off),
+                               vec->elt_size);
+       }
+       copy->len = len;
+
+       return copy;
+
+fail:
+       ec_vec_free(copy);
+       return NULL;
+}
+
+size_t ec_vec_len(const struct ec_vec *vec)
+{
+       if (vec == NULL)
+               return 0;
+
+       return vec->len;
+}
+
+struct ec_vec *ec_vec_dup(const struct ec_vec *vec)
+{
+       return ec_vec_ndup(vec, 0, ec_vec_len(vec));
+}
+
+void ec_vec_free(struct ec_vec *vec)
+{
+       size_t i;
+
+       if (vec == NULL)
+               return;
+
+       for (i = 0; i < ec_vec_len(vec); i++) {
+               if (vec->free)
+                       vec->free(get_obj(vec, i));
+       }
+
+       ec_free(vec->vec);
+       ec_free(vec);
+}
+
+int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx)
+{
+       if (vec == NULL || idx >= vec->len) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       memcpy(ptr, get_obj(vec, idx), vec->elt_size);
+
+       return 0;
+}
+
+static void str_free(void *elt)
+{
+       char **s = elt;
+
+       ec_free(*s);
+}
+
+#define GOTO_FAIL do {                                      \
+               EC_LOG(EC_LOG_ERR, "%s:%d: test failed\n",   \
+                       __FILE__, __LINE__);                 \
+               goto fail;                                   \
+       } while(0)
+
+/* LCOV_EXCL_START */
+static int ec_vec_testcase(void)
+{
+       struct ec_vec *vec = NULL;
+       struct ec_vec *vec2 = NULL;
+       uint8_t val8;
+       uint16_t val16;
+       uint32_t val32;
+       uint64_t val64;
+       void *valp;
+       char *vals;
+
+       /* uint8_t vector */
+       vec = ec_vec(sizeof(val8), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_u8(vec, 0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u8(vec, 1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u8(vec, 2) < 0)
+               GOTO_FAIL;
+       /* should fail */
+       if (ec_vec_add_u16(vec, 3) == 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u32(vec, 3) == 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u64(vec, 3) == 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, (void *)3) == 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&val8, vec, 0) < 0)
+               GOTO_FAIL;
+       if (val8 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec, 1) < 0)
+               GOTO_FAIL;
+       if (val8 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec, 2) < 0)
+               GOTO_FAIL;
+       if (val8 != 2)
+               GOTO_FAIL;
+
+       /* duplicate the vector */
+       vec2 = ec_vec_dup(vec);
+       if (vec2 == NULL)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 0) < 0)
+               GOTO_FAIL;
+       if (val8 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 1) < 0)
+               GOTO_FAIL;
+       if (val8 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 2) < 0)
+               GOTO_FAIL;
+       if (val8 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec2);
+       vec2 = NULL;
+
+       /* dup at offset 1 */
+       vec2 = ec_vec_ndup(vec, 1, 2);
+       if (vec2 == NULL)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 0) < 0)
+               GOTO_FAIL;
+       if (val8 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 1) < 0)
+               GOTO_FAIL;
+       if (val8 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec2);
+       vec2 = NULL;
+
+       /* len = 0, duplicate is empty */
+       vec2 = ec_vec_ndup(vec, 2, 0);
+       if (vec2 == NULL)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 0) == 0)
+               GOTO_FAIL;
+
+       ec_vec_free(vec2);
+       vec2 = NULL;
+
+       /* bad dup args */
+       vec2 = ec_vec_ndup(vec, 10, 1);
+       if (vec2 != NULL)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* uint16_t vector */
+       vec = ec_vec(sizeof(val16), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_u16(vec, 0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u16(vec, 1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u16(vec, 2) < 0)
+               GOTO_FAIL;
+       /* should fail */
+       if (ec_vec_add_u8(vec, 3) == 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&val16, vec, 0) < 0)
+               GOTO_FAIL;
+       if (val16 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val16, vec, 1) < 0)
+               GOTO_FAIL;
+       if (val16 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val16, vec, 2) < 0)
+               GOTO_FAIL;
+       if (val16 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* uint32_t vector */
+       vec = ec_vec(sizeof(val32), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_u32(vec, 0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u32(vec, 1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u32(vec, 2) < 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&val32, vec, 0) < 0)
+               GOTO_FAIL;
+       if (val32 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val32, vec, 1) < 0)
+               GOTO_FAIL;
+       if (val32 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val32, vec, 2) < 0)
+               GOTO_FAIL;
+       if (val32 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* uint64_t vector */
+       vec = ec_vec(sizeof(val64), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_u64(vec, 0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u64(vec, 1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u64(vec, 2) < 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&val64, vec, 0) < 0)
+               GOTO_FAIL;
+       if (val64 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val64, vec, 1) < 0)
+               GOTO_FAIL;
+       if (val64 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val64, vec, 2) < 0)
+               GOTO_FAIL;
+       if (val64 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* ptr vector */
+       vec = ec_vec(sizeof(valp), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_ptr(vec, (void *)0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, (void *)1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, (void *)2) < 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&valp, vec, 0) < 0)
+               GOTO_FAIL;
+       if (valp != (void *)0)
+               GOTO_FAIL;
+       if (ec_vec_get(&valp, vec, 1) < 0)
+               GOTO_FAIL;
+       if (valp != (void *)1)
+               GOTO_FAIL;
+       if (ec_vec_get(&valp, vec, 2) < 0)
+               GOTO_FAIL;
+       if (valp != (void *)2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* string vector */
+       vec = ec_vec(sizeof(valp), 0, NULL, str_free);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_ptr(vec, ec_strdup("0")) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, ec_strdup("1")) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, ec_strdup("2")) < 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&vals, vec, 0) < 0)
+               GOTO_FAIL;
+       if (vals == NULL || strcmp(vals, "0"))
+               GOTO_FAIL;
+       if (ec_vec_get(&vals, vec, 1) < 0)
+               GOTO_FAIL;
+       if (vals == NULL || strcmp(vals, "1"))
+               GOTO_FAIL;
+       if (ec_vec_get(&vals, vec, 2) < 0)
+               GOTO_FAIL;
+       if (vals == NULL || strcmp(vals, "2"))
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* invalid args */
+       vec = ec_vec(0, 0, NULL, NULL);
+       if (vec != NULL)
+               GOTO_FAIL;
+
+       return 0;
+
+fail:
+       ec_vec_free(vec);
+       ec_vec_free(vec2);
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_vec_test = {
+       .name = "vec",
+       .test = ec_vec_testcase,
+};
+
+EC_TEST_REGISTER(ec_vec_test);
diff --git a/libecoli/ecoli_vec.h b/libecoli/ecoli_vec.h
new file mode 100644 (file)
index 0000000..5fdaa99
--- /dev/null
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Vectors of objects.
+ *
+ * The ec_vec API provide helpers to manipulate vectors of objects
+ * of any kind.
+ */
+
+#ifndef ECOLI_VEC_
+#define ECOLI_VEC_
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdio.h>
+
+/* if NULL, default does nothing */
+typedef void (*ec_vec_elt_free_t)(void *ptr);
+
+/* if NULL, default is:
+ * memcpy(dst, src, vec->elt_size)
+ */
+typedef void (*ec_vec_elt_copy_t)(void *dst, void *src);
+
+struct ec_vec *ec_vec(size_t elt_size, size_t size,
+               ec_vec_elt_copy_t copy, ec_vec_elt_free_t free);
+int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr);
+
+int ec_vec_add_ptr(struct ec_vec *vec, void *elt);
+int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt);
+int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt);
+int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt);
+int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt);
+
+int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx);
+
+struct ec_vec *ec_vec_dup(const struct ec_vec *vec);
+struct ec_vec *ec_vec_ndup(const struct ec_vec *vec,
+       size_t off, size_t len);
+void ec_vec_free(struct ec_vec *vec);
+
+__attribute__((pure))
+size_t ec_vec_len(const struct ec_vec *vec);
+
+#endif
diff --git a/test/test.c b/test/test.c
new file mode 100644 (file)
index 0000000..1b2b7ef
--- /dev/null
@@ -0,0 +1,407 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <getopt.h>
+#include <limits.h>
+#include <execinfo.h>
+#include <errno.h>
+
+#include <ecoli_init.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_malloc.h>
+
+/* LCOV_EXCL_START */
+EC_LOG_TYPE_REGISTER(main);
+
+#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 int seed = 0;
+static size_t alloc_success = 0;
+
+static const char ec_short_options[] =
+       "h"  /* help */
+       "l:" /* log-level */
+       "r:" /* random-alloc-fail */
+       "s:" /* seed */
+       ;
+
+#define EC_OPT_HELP "help"
+#define EC_OPT_LOG_LEVEL "log-level"
+#define EC_OPT_RANDOM_ALLOC_FAIL "random-alloc-fail"
+#define EC_OPT_SEED "seed"
+
+static const struct option ec_long_options[] = {
+       {EC_OPT_HELP, 1, NULL, 'h'},
+       {EC_OPT_LOG_LEVEL, 1, NULL, 'l'},
+       {EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'},
+       {EC_OPT_SEED, 1, NULL, 's'},
+       {NULL, 0, NULL, 0}
+};
+
+static void usage(const char *prgname)
+{
+       printf("%s [options] [test1 test2 test3...]\n"
+               "  -h\n"
+               "  --"EC_OPT_HELP"\n"
+               "      Show this help.\n"
+               "  -l <level>\n"
+               "  --"EC_OPT_LOG_LEVEL"=<level>\n"
+               "      Set log level (0 = no log, 7 = verbose).\n"
+               "  -r <probability>\n"
+               "  --"EC_OPT_RANDOM_ALLOC_FAIL"=<probability>\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"
+               "  -s <seed>\n"
+               "  --seed=<seed>\n"
+               "      Seeds the random number generator. Default is 0.\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 int parse_args(int argc, char **argv)
+{
+       int ret, opt;
+
+       while ((opt = getopt_long(argc, argv, ec_short_options,
+                               ec_long_options, NULL)) != EOF) {
+
+               switch (opt) {
+               case 'h': /* help */
+                       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;
+
+               case 's': /* seed */
+                       if (parse_int(optarg, 0, INT_MAX, &seed, 10) < 0) {
+                               printf("Invalid seed value\n");
+                               usage(argv[0]);
+                               exit(1);
+                       }
+                       break;
+
+               default:
+                       usage(argv[0]);
+                       return -1;
+               }
+       }
+
+       ret = optind - 1;
+       optind = 1;
+
+       return ret;
+}
+
+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 seq;
+       unsigned int line;
+       size_t size;
+       void *stack[STACK_SZ];
+       int stacklen;
+       unsigned int cookie;
+};
+
+struct debug_alloc_ftr {
+       unsigned int cookie;
+} __attribute__((packed));
+
+static int malloc_seq;
+
+static void *debug_malloc(size_t size, const char *file, unsigned int line)
+{
+       struct debug_alloc_hdr *hdr;
+       struct debug_alloc_ftr *ftr;
+       size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
+       void *ret;
+       int r = random();
+
+       if (alloc_fail_proba != 0 && (r % 100) < alloc_fail_proba)
+               hdr = NULL;
+       else
+               hdr = malloc(new_size);
+
+       if (hdr == NULL) {
+               ret = NULL;
+       } else {
+               hdr->seq = malloc_seq;
+               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 = 0x87654321;
+       }
+
+       EC_LOG(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p seq=%d\n",
+               file, line, size, ret, malloc_seq++);
+
+       if (ret)
+               alloc_success++;
+       return ret;
+}
+
+static void debug_free(void *ptr, const char *file, unsigned int line)
+{
+       struct debug_alloc_hdr *hdr, *h;
+       struct debug_alloc_ftr *ftr;
+
+       (void)file;
+       (void)line;
+
+       EC_LOG(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
+
+       if (ptr == NULL)
+               return;
+
+       hdr = (ptr - sizeof(*hdr));
+       if (hdr->cookie != 0x12345678) {
+               EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
+                       file, line, ptr);
+               abort();
+       }
+
+       ftr = (ptr + hdr->size);
+       if (ftr->cookie != 0x87654321) {
+               EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
+                       file, line, ptr);
+               abort();
+       }
+
+       TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
+               if (h == hdr)
+                       break;
+       }
+
+       if (h == NULL) {
+               EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
+                       file, line, ptr);
+               abort();
+       }
+
+       TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
+       free(hdr);
+}
+
+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;
+       size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
+       void *ret;
+
+       if (ptr != NULL) {
+               hdr =  (ptr - sizeof(*hdr));
+               if (hdr->cookie != 0x12345678) {
+                       EC_LOG(EC_LOG_ERR,
+                               "%s:%d: error: realloc(%p): bad start cookie\n",
+                               file, line, ptr);
+                       abort();
+               }
+
+               ftr = (ptr + hdr->size);
+               if (ftr->cookie != 0x87654321) {
+                       EC_LOG(EC_LOG_ERR,
+                               "%s:%d: error: realloc(%p): bad end cookie\n",
+                               file, line, ptr);
+                       abort();
+               }
+
+               TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
+                       if (h == hdr)
+                               break;
+               }
+
+               if (h == NULL) {
+                       EC_LOG(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
+                               file, line, ptr);
+                       abort();
+               }
+
+               TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
+               hdr = realloc(hdr, new_size);
+               if (hdr == NULL) {
+                       TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
+                       ret = NULL;
+               } else {
+                       ret = hdr + 1;
+               }
+       } else {
+               hdr = realloc(NULL, new_size);
+               if (hdr == NULL)
+                       ret = NULL;
+               else
+                       ret = hdr + 1;
+       }
+
+       if (hdr != NULL) {
+               hdr->seq = malloc_seq;
+               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 = 0x87654321;
+       }
+
+       EC_LOG(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p seq=%d\n",
+               file, line, ptr, size, ret, malloc_seq++);
+
+       if (ret)
+               alloc_success++;
+       return ret;
+}
+
+static int debug_alloc_dump_leaks(void)
+{
+       struct debug_alloc_hdr *hdr;
+       int i;
+       char **buffer;
+
+       EC_LOG(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
+
+       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 seq=%u size=%zd ptr=%p\n",
+                       hdr->file, hdr->line, hdr->seq, 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 <prog> <addr>\n");
+
+       return -1;
+}
+
+static int debug_log(int type, unsigned int level, void *opaque,
+               const char *str)
+{
+       (void)type;
+       (void)opaque;
+
+       if (level > (unsigned int)log_level)
+               return 0;
+
+       if (printf("%s", str) < 0)
+               return -1;
+
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i, ret = 0, leaks;
+
+       ret = parse_args(argc, argv);
+       if (ret < 0)
+               return 1;
+
+       argc -= ret;
+       argv += ret;
+
+       srandom(seed);
+
+       /* 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;
+       }
+
+       if (ec_init() < 0) {
+               fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
+               return 1;
+       }
+       ec_log_fct_register(debug_log, NULL);
+
+       ret = 0;
+       if (argc <= 1) {
+               ret = ec_test_all();
+       } else {
+               for (i = 1; i < argc; i++)
+                       ret |= ec_test_one(argv[i]);
+       }
+
+       leaks = debug_alloc_dump_leaks();
+
+       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;
+}
+/* LCOV_EXCL_STOP */
diff --git a/test/test.sh b/test/test.sh
new file mode 100755 (executable)
index 0000000..83a7538
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+
+set -e
+
+SEED=100
+while [ ${SEED} -gt 0 ]; do
+       CMD="./build/test --random-alloc-fail=1 --seed=${SEED} $*"
+       ${CMD} --log-level=0 || (
+               echo "=== test failed, replay seed=${SEED} with logs ===" &&
+               ${CMD} --log-level=6 ||
+               echo "=== test failed: ${CMD}" &&
+               false
+       )
+
+       SEED=$((SEED-1)) && continue
+done
diff --git a/todo.txt b/todo.txt
new file mode 100644 (file)
index 0000000..4e5d94f
--- /dev/null
+++ b/todo.txt
@@ -0,0 +1,421 @@
+tk_cmd
+======
+
+X evaluate expression tree in ec_tk_expr
+X cmd token
+- example
+X tk_re
+
+cleanup / rework
+================
+
+X ec_completed_item_update()
+X ec_completed_item_set_display_value()
+X add_no_match
+X add_partial_match
+- check XXX in code
+X properly manage quotes in shlex
+X remove the _new() functions
+X iterate children nodes without chaining them
+- add a node vector type: will be used in several nodes (ex: or, seq, ...)
+- check allocation model everywhere
+- checkpatch?
+- use linux style (update .emacs)
+- better logs
+- check return values (-1 or NULL) + use errno
+- check missing static / const
+- license: SPDX
+- check all completion nodes
+X split ecoli_tk.h
+- size_t or unsigned int?
+X rename:
+  X ec_tk -> ec_node
+  X ec_parsed_tk -> ec_parsed
+  X ec_completed_tk -> ec_completed
+  X tk, gen_tk, token, ... -> node
+  X tokens -> input_str / input_strvec ?
+X save node path in completion to fix help string
+- code coverage
+- try to hide structures
+- anything better than weakref?
+- add ec_node_defaults.[ch] providing usual implementations of node methods
+X use vec for strvec
+- ELOOP in case of loop
+- remove weakref?
+- sh_lex to provide offsets in attributes
+- accessors for all structs
+
+dependencies
+============
+
+X pass the current parsed state when parsing/completing
+X new node "once"
+- new node "condition"
+
+logs
+====
+
+X register log types
+
+yaml
+====
+
+X register nodes by name
+- interface to add attributes: all nodes must be configurable through a
+  generic api
+  - attr string
+  - attr string list
+  - attr node
+  - attr node list
+  - attr int
+
+- yaml interface to create nodes
+- example
+
+examples
+========
+
+- example which parses arguments (argc/argv)
+- example that acts as bash completion (ip link ?)
+- calculator example (var assignation, expression evaluation)
+- example with libedit
+- mini script language
+- configuration file
+- mini shell: cd, ls, cat, stat
+- mini network console based on ip
+
+doc
+===
+
+- overview
+- add api doc in .h
+- generate automatic api doc
+- architecture
+- coding rules, process
+- each node
+- allocation model
+- say that it stops at first match (no ambigous support)
+- say that completion must be exhaustive
+
+build framework
+===============
+
+- .map files for API
+- split libs, tests and examples
+- add make help
+- add make config
+- -fvisibility=
+
+tests
+=====
+
+- complete automatic tests with "make test"
+
+new nodes
+=========
+
+- regexp
+- node which always matches
+- file + partial completion
+- ether, ip, network
+- fusion node: need to match several children, same for completion
+- float
+- not
+
+encoding
+========
+
+- support utf-8 and other encodings
+- example
+- documentation
+
+netconf example
+===============
+
+- demonstration example that parses yang file and generate cli
+
+
+
+-----------------------
+
+readline:
+
+[tab]  list possible completions (matches/partial only)
+[?]    list what is expected, example:
+
+"command [foo] toto|titi|<int>"
+
+help("command f") ->
+  foo     (help of foo)
+  toto    (help of toto)
+  titi    (help of titi)
+  <int>   (help of int)
+
+
+----------------
+
+struct names
+============
+
+ideas:
+
+- ec_node: a node that can be parsed/completed
+- ec_parse: a tree describing the result of parse(node, input)
+- ec_comp: a list describing the result of complete(node, input)
+
+ec_comp_item
+
+
+---------------
+
+node tree
+=========
+
+Example:
+
+1  seq
+2    option
+3      str(foo)
+4    or
+5      int(1,10)
+6      str(bar)
+7      str(foo)
+
+parse() returns a tree
+=======
+
+- each node of the tree refers to a ec_node
+- each node points to the strvec that matches
+- parse returns the first matching solution
+- usually try to match as many str in the vecs (seq node)
+
+[foo] ->
+1 seq
+2   option
+4   or
+7     str(foo)
+
+The parse cb of the node is:
+
+parse_cb(node, current_parse_state, strvec, *nmatch)
+
+return values:
+- 0: success, child->strvec is set by node (NULL = no_match)
+- -1: error (errno is set)
+maybe complex to use:
+- the node must set the match (ex: "return ec_parsed_node_match()")
+- the caller must use accessor to check if it matches or not
+
+alternative idea for return values:
+- >= 0: match, ret == nb_tk
+- -1: error (errno is set)
+- -2 or MAX_INT: success, but no match
+This is strange to have a specific value for no match
+With MAX_INT, this is the best (less bad) alternative
+
+alternative idea for return values:
+- ec_parse_result_match(n_tokens >= 0)
+- ec_parse_result_nomatch()
+- ec_parse_result_error(errno)
+
+A node always try to consume the maximum number of tokens.
+Example:
+1  seq
+2    option
+3      str(foo)
+4    str(foo)
+5    str(bar)
+
+[foo, foo, bar] matches
+[foo, bar] does *not* match
+
+complete() returns a list of possible completions
+==========
+
+problems:
+- partial completion: in a path dir/file, completion stops once
+  after the directory
+- displayed value is not the completion token: when completing a
+  file in several subdirectories, the full path is not displayed
+- any parent node can modify the completions, ex: add missing quotes
+  in ec_node_sh_lex(), filter completions in case of a ec_node_filter()
+- a command line may want to display the help from the most specific
+  token, or not.
+- some specific nodes can complete several tokens
+
+struct item {
+  const char *str;
+  type: full, partial, unknown
+}
+
+full: the completion item matches token
+partial: beginning of a completion, does not match the token
+         (good example is a directory in a path)
+unknown: could complete, but the node does not know how
+
+struct completion_item {
+  const char *value;
+  const char *disp;
+}
+
+struct completed_elt {
+  ec_parsed *parse_tree; // current tree state
+  ec_node *last;         // last node of the tree
+  list of items;         // list of items for this parse tree
+}
+
+struct completed {
+  list(elt)
+}
+
+The callback is:
+
+complete_cb(node, current_complete_state, current_parse_state, strvec)
+return:
+- 0 = success, the current complete state is updated
+- -1 = error (set errno?)
+
+
+a node can filter the completions
+
+
+[] ->
+  foo   3 str(foo)
+    seq
+      option
+        str(foo) <-
+
+  ""    5 int(1,10)
+    seq
+      option
+      or
+        int <-
+
+  bar   6 str(bar)
+  foo   7 str(bar)
+...
+
+
+[foo, ] ->
+
+  ?       5 int(1,10)
+  seq
+    option
+      str(foo)
+    or
+      int <-
+
+  bar   6 str(bar)
+  foo   7 str(bar)
+
+
+
+-----
+
+changes:
+- a completion item should contain a strvec for the value
+  (the display string remains a string)
+- there is maybe no good reason to split in:
+  - ec_completed_item()
+  - ec_completed_item_set()
+  - ec_completed_item_set_display()
+  - ec_completed_item_add()
+
+-----
+
+sh_lex
+  or
+    str(foo)
+    str(foo2)
+    str(bar)
+
+complete(sh_lex, ["'fo"])
+  complete(sh_lex, ["fo"]) -> ["foo", "foo2"]
+  
+
+-----
+
+#include <stdio.h>
+#include <stdbool.h>
+
+
+struct res {
+       int a;
+};
+
+static inline bool is_success(struct res r)
+{
+       if (r.a == 0)
+               return true;
+       return false;
+}
+
+
+static inline struct res res(int a)
+{
+       struct res r;
+       r.a = a;
+       return r;
+}
+
+int main(void)
+{
+       struct res r;
+
+       r = res(0);
+
+       printf("%d\n", r.a);
+       if (is_success(r))
+               printf("success: %d\n", r.a);
+
+       r = res(1);
+
+       printf("%d\n", r.a);
+       if (is_success(r))
+               printf("success: %d\n", r.a);
+
+       return 0;
+}
+
+
+----
+
+
+expr expr expr
+
+[toto] | tutu
+
+[toto [titi]]
+
+
+
+pre_op = "!"
+post_op = "^"
+post = val |
+       pre_op expr |
+       "(" expr ")"
+term = post post_op*
+prod = term ( "*" term )*
+sum = prod ( "+" prod )*
+expr = sum
+
+
+-----
+
+break on malloc:
+
+b debug_malloc
+# or: b debug_realloc
+condition <breakoint num> malloc_seq >= <value>
+
+alternative
+
+watch malloc_seq
+condition <watchpoint num> malloc_seq == <value + 1>
+run <args...>
+c
+
+
+---------------
+
+