--- /dev/null
+# 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
--- /dev/null
+/* 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;
+
+}
--- /dev/null
+#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;
+}
--- /dev/null
+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
+++ /dev/null
-# 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
+++ /dev/null
-/* 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 */
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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_ */
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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 */
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-
-}
+++ /dev/null
-/* 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 */
+++ /dev/null
-#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;
-}
+++ /dev/null
-#!/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
+++ /dev/null
-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
-
-
----------------
-
-
--- /dev/null
+/* 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 */
+}
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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_ */
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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
--- /dev/null
+/* 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);
--- /dev/null
+/* 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
--- /dev/null
+/* 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 */
--- /dev/null
+#!/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
--- /dev/null
+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
+
+
+---------------
+
+