From: Olivier Matz Date: Sun, 12 Aug 2018 13:08:26 +0000 (+0200) Subject: reorganize sources X-Git-Url: http://git.droids-corp.org/?a=commitdiff_plain;h=90efcb0b905753a2eac864bc1a869781d7c31919;p=protos%2Flibecoli.git reorganize sources --- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bb446cf --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2016, Olivier MATZ + +ECOLI ?= $(abspath ..) +include $(ECOLI)/mk/ecoli-pre.mk + +# output path with trailing slash +O ?= build/ + +# XXX -O0 +CFLAGS = -g -O0 -Wall -Werror -W -Wextra -fPIC -Wmissing-prototypes +CFLAGS += -I. + +# XXX coverage +CFLAGS += --coverage +LDFLAGS += --coverage +# rm -rf build; rm -rf result; make && ./build/test +# lcov -d build -c -t build/test -o test.info && genhtml -o result test.info + + +srcs := +srcs += ecoli_assert.c +srcs += ecoli_complete.c +srcs += ecoli_config.c +srcs += ecoli_keyval.c +srcs += ecoli_init.c +srcs += ecoli_log.c +srcs += ecoli_malloc.c +srcs += ecoli_murmurhash.c +srcs += ecoli_strvec.c +srcs += ecoli_test.c +srcs += ecoli_node.c +srcs += ecoli_node_any.c +srcs += ecoli_node_cmd.c +srcs += ecoli_node_empty.c +srcs += ecoli_node_expr.c +srcs += ecoli_node_expr_test.c +srcs += ecoli_node_dynamic.c +srcs += ecoli_node_file.c +srcs += ecoli_node_helper.c +srcs += ecoli_node_int.c +srcs += ecoli_node_many.c +srcs += ecoli_node_none.c +srcs += ecoli_node_once.c +srcs += ecoli_node_option.c +srcs += ecoli_node_or.c +srcs += ecoli_node_re.c +srcs += ecoli_node_re_lex.c +srcs += ecoli_node_seq.c +srcs += ecoli_node_sh_lex.c +srcs += ecoli_node_space.c +srcs += ecoli_node_str.c +srcs += ecoli_node_subset.c +srcs += ecoli_parse.c +srcs += ecoli_string.c +srcs += ecoli_vec.c + +shlib-y-$(O)libecoli.so := $(srcs) + +ldflags-$(O)test = -rdynamic +exe-y-$(O)test = $(srcs) main.c + +ldflags-$(O)readline = -lreadline -ltermcap +exe-y-$(O)readline = $(srcs) main-readline.c + +ldflags-$(O)parse-yaml = -lyaml +exe-y-$(O)parse-yaml = $(srcs) parse-yaml.c + +include $(ECOLI)/mk/ecoli-post.mk + +all: _ecoli_all + +clean: _ecoli_clean + +.PHONY: clean all diff --git a/examples/readline/main.c b/examples/readline/main.c new file mode 100644 index 0000000..2c81c53 --- /dev/null +++ b/examples/readline/main.c @@ -0,0 +1,361 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#define _GNU_SOURCE /* for asprintf */ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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] = ""; + + while ((item = ec_comp_iter_next(iter)) != NULL) { + char **tmp; + + /* keep one help per group, skip other items */ + grp = ec_comp_item_get_grp(item); + if (grp == prev_grp) + continue; + + prev_grp = grp; + + tmp = realloc(helps, (count + match + 2) * sizeof(char *)); + if (tmp == NULL) + goto fail; + helps = tmp; + helps[count + match + 1] = get_node_help(item); + count++; + } + + ec_comp_iter_free(iter); + ec_comp_free(c); + /* ensure not more than 1 entry per line */ + rl_get_screen_size(NULL, &cols); + rl_display_match_list(helps, count + match, cols); + rl_forced_update_display(); + + return 0; + +fail: + ec_comp_iter_free(iter); + ec_parse_free(p); + free(line); + ec_comp_free(c); + if (helps != NULL) { + while (count--) + free(helps[count + match + 1]); + } + free(helps); + + return 1; +} + +static int create_commands(void) +{ + struct ec_node *cmdlist = NULL, *cmd = NULL; + + cmdlist = ec_node("or", EC_NO_ID); + if (cmdlist == NULL) + goto fail; + + + cmd = EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "hello"), + EC_NODE_OR("name", + ec_node_str("john", "john"), + ec_node_str(EC_NO_ID, "johnny"), + ec_node_str(EC_NO_ID, "mike") + ), + ec_node_option(EC_NO_ID, ec_node_int("int", 0, 10, 10)) + ); + if (cmd == NULL) + goto fail; + ec_keyval_set(ec_node_attrs(cmd), "help", + "say hello to someone several times", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "john")), + "help", "specific help for john", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), + "help", "the name of the person", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "int")), + "help", "an integer (0-10)", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_CMD(EC_NO_ID, "good morning name [count]", + EC_NODE_CMD("name", "bob|bobby|michael"), + ec_node_int("count", 0, 10, 10)); + if (cmd == NULL) + goto fail; + ec_keyval_set(ec_node_attrs(cmd), "help", + "say good morning to someone several times", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), "help", + "the person to greet", NULL); + ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "count")), "help", + "how many times to greet (0-10)", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_CMD(EC_NO_ID, + "buy potatoes,carrots,pumpkins"); + if (cmd == NULL) + goto fail; + ec_keyval_set(ec_node_attrs(cmd), "help", + "buy some vegetables", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_CMD(EC_NO_ID, "eat vegetables", + ec_node_many("vegetables", + EC_NODE_OR(EC_NO_ID, + ec_node_str(EC_NO_ID, "potatoes"), + ec_node_once(EC_NO_ID, + ec_node_str(EC_NO_ID, "carrots")), + ec_node_once(EC_NO_ID, + ec_node_str(EC_NO_ID, "pumpkins"))), + 1, 0)); + if (cmd == NULL) + goto fail; + ec_keyval_set(ec_node_attrs(cmd), "help", + "eat vegetables (take some more potatoes)", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "bye") + ); + ec_keyval_set(ec_node_attrs(cmd), "help", "say bye", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + cmd = EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "load"), + ec_node("file", EC_NO_ID) + ); + ec_keyval_set(ec_node_attrs(cmd), "help", "load a file", NULL); + if (ec_node_or_add(cmdlist, cmd) < 0) + goto fail; + + + commands = ec_node_sh_lex(EC_NO_ID, cmdlist); + if (commands == NULL) + goto fail; + + return 0; + + fail: + fprintf(stderr, "cannot initialize nodes\n"); + ec_node_free(cmdlist); + return -1; +} + +int main(void) +{ + struct ec_parse *p; + char *line; + + if (ec_init() < 0) { + fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno)); + return 1; + } + + if (create_commands() < 0) + return 1; + + rl_bind_key('?', show_help); + rl_attempted_completion_function = my_attempted_completion; + + while (1) { + line = readline("> "); + if (line == NULL) + break; + + p = ec_node_parse(commands, line); + ec_parse_dump(stdout, p); + add_history(line); + ec_parse_free(p); + } + + + ec_node_free(commands); + return 0; + +} diff --git a/examples/yaml/parse-yaml.c b/examples/yaml/parse-yaml.c new file mode 100644 index 0000000..48bcdc7 --- /dev/null +++ b/examples/yaml/parse-yaml.c @@ -0,0 +1,552 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* associate a yaml node to a ecoli node */ +struct pair { + const yaml_node_t *ynode; + struct ec_node *enode; +}; + +/* store the ecoli node tree and the associations yaml_node <-> ec_node */ +struct enode_tree { + struct ec_node *root; + struct pair *table; + size_t table_len; +}; + +static struct ec_node * +parse_ec_node(struct enode_tree *tree, + const yaml_document_t *document, const yaml_node_t *ynode); + +static struct ec_config * +parse_ec_config_list(struct enode_tree *tree, + const struct ec_config_schema *schema, + const yaml_document_t *document, const yaml_node_t *ynode); + +static struct ec_config * +parse_ec_config_dict(struct enode_tree *tree, + const struct ec_config_schema *schema, + const yaml_document_t *document, const yaml_node_t *ynode); + +/* XXX to utils.c ? */ +static int +parse_llint(const char *str, int64_t *val) +{ + char *endptr; + int save_errno = errno; + + errno = 0; + *val = strtoll(str, &endptr, 0); + + if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) || + (errno != 0 && *val == 0)) + return -1; + + if (*endptr != 0) { + errno = EINVAL; + return -1; + } + + errno = save_errno; + return 0; +} + +static int +parse_ullint(const char *str, uint64_t *val) +{ + char *endptr; + int save_errno = errno; + + /* since a negative input is silently converted to a positive + * one by strtoull(), first check that it is positive */ + if (strchr(str, '-')) + return -1; + + errno = 0; + *val = strtoull(str, &endptr, 0); + + if ((errno == ERANGE && *val == ULLONG_MAX) || + (errno != 0 && *val == 0)) + return -1; + + if (*endptr != 0) + return -1; + + errno = save_errno; + return 0; +} + +static int +parse_bool(const char *str, bool *val) +{ + if (!strcasecmp(str, "true")) { + *val = true; + return 0; + } else if (!strcasecmp(str, "false")) { + *val = false; + return 0; + } + errno = EINVAL; + return -1; +} + +static int +add_in_table(struct enode_tree *tree, const yaml_node_t *ynode, + struct ec_node *enode) +{ + struct pair *table = NULL; + + table = realloc(tree->table, (tree->table_len + 1) * sizeof(*table)); + if (table == NULL) + return -1; + + ec_node_clone(enode); + table[tree->table_len].ynode = ynode; + table[tree->table_len].enode = enode; + tree->table = table; + tree->table_len++; + + return 0; +} + +static void +free_tree(struct enode_tree *tree) +{ + size_t i; + + if (tree->root != NULL) + ec_node_free(tree->root); + for (i = 0; i < tree->table_len; i++) + ec_node_free(tree->table[i].enode); + free(tree->table); +} + +static struct ec_config * +parse_ec_config(struct enode_tree *tree, + const struct ec_config_schema *schema_elt, + const yaml_document_t *document, const yaml_node_t *ynode) +{ + const struct ec_config_schema *subschema; + struct ec_config *config = NULL; + struct ec_node *enode = NULL; + enum ec_config_type type; + const char *value_str; + uint64_t u64; + int64_t i64; + bool boolean; + + type = ec_config_schema_type(schema_elt); + + switch (type) { + case EC_CONFIG_TYPE_BOOL: + if (ynode->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Boolean should be scalar\n"); + goto fail; + } + value_str = (const char *)ynode->data.scalar.value; + if (parse_bool(value_str, &boolean) < 0) { + fprintf(stderr, "Failed to parse boolean\n"); + goto fail; + } + config = ec_config_bool(boolean); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_INT64: + if (ynode->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Int64 should be scalar\n"); + goto fail; + } + value_str = (const char *)ynode->data.scalar.value; + if (parse_llint(value_str, &i64) < 0) { + fprintf(stderr, "Failed to parse i64\n"); + goto fail; + } + config = ec_config_i64(i64); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_UINT64: + if (ynode->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Uint64 should be scalar\n"); + goto fail; + } + value_str = (const char *)ynode->data.scalar.value; + if (parse_ullint(value_str, &u64) < 0) { + fprintf(stderr, "Failed to parse u64\n"); + goto fail; + } + config = ec_config_u64(u64); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_STRING: + if (ynode->type != YAML_SCALAR_NODE) { + fprintf(stderr, "String should be scalar\n"); + goto fail; + } + value_str = (const char *)ynode->data.scalar.value; + config = ec_config_string(value_str); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_NODE: + enode = parse_ec_node(tree, document, ynode); + if (enode == NULL) + goto fail; + config = ec_config_node(enode); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_LIST: + subschema = ec_config_schema_sub(schema_elt); + if (subschema == NULL) { + fprintf(stderr, "List has no subschema\n"); + goto fail; + } + config = parse_ec_config_list(tree, subschema, document, ynode); + if (config == NULL) + goto fail; + break; + case EC_CONFIG_TYPE_DICT: + subschema = ec_config_schema_sub(schema_elt); + if (subschema == NULL) { + fprintf(stderr, "Dict has no subschema\n"); + goto fail; + } + config = parse_ec_config_dict(tree, subschema, document, ynode); + if (config == NULL) + goto fail; + break; + default: + fprintf(stderr, "Invalid config type %d\n", type); + goto fail; + } + + return config; + +fail: + ec_node_free(enode); + ec_config_free(config); + return NULL; +} + +static struct ec_config * +parse_ec_config_list(struct enode_tree *tree, + const struct ec_config_schema *schema, + const yaml_document_t *document, const yaml_node_t *ynode) +{ + struct ec_config *config = NULL, *subconfig = NULL; + const yaml_node_item_t *item; + const yaml_node_t *value; + + (void)tree; + (void)schema; + (void)document; + + if (ynode->type != YAML_SEQUENCE_NODE) { + fprintf(stderr, "Ecoli list config should be a yaml sequence\n"); + goto fail; + } + + config = ec_config_list(); + if (config == NULL) { + fprintf(stderr, "Failed to allocate config\n"); + goto fail; + } + + for (item = ynode->data.sequence.items.start; + item < ynode->data.sequence.items.top; item++) { + value = document->nodes.start + (*item) - 1; // XXX -1 ? + subconfig = parse_ec_config(tree, schema, document, value); + if (subconfig == NULL) + goto fail; + if (ec_config_list_add(config, subconfig) < 0) { + fprintf(stderr, "Failed to add list entry\n"); + goto fail; + } + } + + return config; + +fail: + ec_config_free(config); + return NULL; +} + +static struct ec_config * +parse_ec_config_dict(struct enode_tree *tree, + const struct ec_config_schema *schema, + const yaml_document_t *document, const yaml_node_t *ynode) +{ + const struct ec_config_schema *schema_elt; + struct ec_config *config = NULL, *subconfig = NULL; + const yaml_node_t *key, *value; + const yaml_node_pair_t *pair; + const char *key_str; + + if (ynode->type != YAML_MAPPING_NODE) { + fprintf(stderr, "Ecoli config should be a yaml mapping node\n"); + goto fail; + } + + config = ec_config_dict(); + if (config == NULL) { + fprintf(stderr, "Failed to allocate config\n"); + goto fail; + } + + for (pair = ynode->data.mapping.pairs.start; + pair < ynode->data.mapping.pairs.top; pair++) { + key = document->nodes.start + pair->key - 1; // XXX -1 ? + value = document->nodes.start + pair->value - 1; + key_str = (const char *)key->data.scalar.value; + + if (ec_config_key_is_reserved(key_str)) + continue; + schema_elt = ec_config_schema_lookup(schema, key_str); + if (schema_elt == NULL) { + fprintf(stderr, "No such config %s\n", key_str); + goto fail; + } + subconfig = parse_ec_config(tree, schema_elt, document, value); + if (subconfig == NULL) + goto fail; + if (ec_config_dict_set(config, key_str, subconfig) < 0) { + fprintf(stderr, "Failed to set dict entry\n"); + goto fail; + } + } + + return config; + +fail: + ec_config_free(config); + return NULL; +} + +static struct ec_node * +parse_ec_node(struct enode_tree *tree, + const yaml_document_t *document, const yaml_node_t *ynode) +{ + const struct ec_config_schema *schema; + const struct ec_node_type *type = NULL; + const char *id = NULL, *help = NULL; + struct ec_config *config = NULL; + const yaml_node_t *attrs = NULL; + const yaml_node_t *key, *value; + const yaml_node_pair_t *pair; + const char *key_str, *value_str; + struct ec_node *enode = NULL; + + if (ynode->type != YAML_MAPPING_NODE) { + fprintf(stderr, "Ecoli node should be a yaml mapping node\n"); + goto fail; + } + + for (pair = ynode->data.mapping.pairs.start; + pair < ynode->data.mapping.pairs.top; pair++) { + key = document->nodes.start + pair->key - 1; // XXX -1 ? + value = document->nodes.start + pair->value - 1; + key_str = (const char *)key->data.scalar.value; + value_str = (const char *)value->data.scalar.value; + + if (!strcmp(key_str, "type")) { + if (type != NULL) { + fprintf(stderr, "Duplicate type\n"); + goto fail; + } + if (value->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Type must be a string\n"); + goto fail; + } + type = ec_node_type_lookup(value_str); + if (type == NULL) { + fprintf(stderr, "Cannot find type %s\n", + value_str); + goto fail; + } + } else if (!strcmp(key_str, "attrs")) { + if (attrs != NULL) { + fprintf(stderr, "Duplicate attrs\n"); + goto fail; + } + if (value->type != YAML_MAPPING_NODE) { + fprintf(stderr, "Attrs must be a maping\n"); + goto fail; + } + attrs = value; + } else if (!strcmp(key_str, "id")) { + if (id != NULL) { + fprintf(stderr, "Duplicate id\n"); + goto fail; + } + if (value->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Id must be a scalar\n"); + goto fail; + } + id = value_str; + } else if (!strcmp(key_str, "help")) { + if (help != NULL) { + fprintf(stderr, "Duplicate help\n"); + goto fail; + } + if (value->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Help must be a scalar\n"); + goto fail; + } + help = value_str; + } + } + + /* create the ecoli node */ + if (id == NULL) + id = EC_NO_ID; + enode = ec_node_from_type(type, id); + if (enode == NULL) { + fprintf(stderr, "Cannot create ecoli node\n"); + goto fail; + } + if (add_in_table(tree, ynode, enode) < 0) { + fprintf(stderr, "Cannot add node in table\n"); + goto fail; + } + if (tree->root == NULL) { + ec_node_clone(enode); + tree->root = enode; + } + + /* create its config */ + schema = ec_node_type_schema(type); + if (schema == NULL) { + fprintf(stderr, "No configuration schema for type %s\n", + ec_node_type_name(type)); + goto fail; + } + + config = parse_ec_config_dict(tree, schema, document, ynode); + if (config == NULL) + goto fail; + + if (ec_node_set_config(enode, config) < 0) { + fprintf(stderr, "Failed to set config\n"); + goto fail; + } + + /* add attributes (all as string) */ + //XXX + + return enode; + +fail: + ec_node_free(enode); + ec_config_free(config); + return NULL; +} + +static int +parse_document(struct enode_tree *tree, const yaml_document_t *document) +{ + yaml_node_t *node; + + node = document->nodes.start; + if (parse_ec_node(tree, document, node) == NULL) + return -1; + + return 0; +} + +static int parse_file(struct enode_tree *tree, const char *filename) +{ + FILE *file; + yaml_parser_t parser; + yaml_document_t document; + + file = fopen(filename, "rb"); + if (file == NULL) { + fprintf(stderr, "Failed to open file %s\n", filename); + goto fail_no_doc; + } + + if (yaml_parser_initialize(&parser) == 0) { + fprintf(stderr, "Failed to initialize yaml parser\n"); + goto fail_no_doc; + } + + yaml_parser_set_input_file(&parser, file); + + if (yaml_parser_load(&parser, &document) == 0) { + fprintf(stderr, "Failed to load yaml document\n"); + goto fail_no_doc; + } + + if (yaml_document_get_root_node(&document) == NULL) { + fprintf(stderr, "Incomplete document\n"); //XXX check err + goto fail; + } + + if (parse_document(tree, &document) < 0) { + fprintf(stderr, "Failed to parse document\n"); + goto fail; + } + + yaml_document_delete(&document); + yaml_parser_delete(&parser); + fclose(file); + + return 0; + +fail: + yaml_document_delete(&document); +fail_no_doc: + yaml_parser_delete(&parser); + if (file != NULL) + fclose(file); + + return -1; +} + +int +main(int argc, char *argv[]) +{ + struct enode_tree tree; + + memset(&tree, 0, sizeof(tree)); + + if (argc != 2) { + fprintf(stderr, "Invalid args\n"); + goto fail; + } + if (parse_file(&tree, argv[1]) < 0) { + fprintf(stderr, "Failed to parse file\n"); + goto fail; + } + printf("root=%p len=%zd\n", tree.root, tree.table_len); + ec_node_dump(stdout, tree.root); + free_tree(&tree); + + return 0; + +fail: + free_tree(&tree); + return 1; +} diff --git a/examples/yaml/test.yaml b/examples/yaml/test.yaml new file mode 100644 index 0000000..072fbce --- /dev/null +++ b/examples/yaml/test.yaml @@ -0,0 +1,16 @@ +type: seq +attrs: + toto: 1 + titi: 2 +help: Say hello to someone +children: +- type: str + string: hello +- type: or + id: name + help: Name of the person to greet + children: + - type: str + string: john + - type: str + string: mike diff --git a/lib/Makefile b/lib/Makefile deleted file mode 100644 index bb446cf..0000000 --- a/lib/Makefile +++ /dev/null @@ -1,75 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2016, Olivier MATZ - -ECOLI ?= $(abspath ..) -include $(ECOLI)/mk/ecoli-pre.mk - -# output path with trailing slash -O ?= build/ - -# XXX -O0 -CFLAGS = -g -O0 -Wall -Werror -W -Wextra -fPIC -Wmissing-prototypes -CFLAGS += -I. - -# XXX coverage -CFLAGS += --coverage -LDFLAGS += --coverage -# rm -rf build; rm -rf result; make && ./build/test -# lcov -d build -c -t build/test -o test.info && genhtml -o result test.info - - -srcs := -srcs += ecoli_assert.c -srcs += ecoli_complete.c -srcs += ecoli_config.c -srcs += ecoli_keyval.c -srcs += ecoli_init.c -srcs += ecoli_log.c -srcs += ecoli_malloc.c -srcs += ecoli_murmurhash.c -srcs += ecoli_strvec.c -srcs += ecoli_test.c -srcs += ecoli_node.c -srcs += ecoli_node_any.c -srcs += ecoli_node_cmd.c -srcs += ecoli_node_empty.c -srcs += ecoli_node_expr.c -srcs += ecoli_node_expr_test.c -srcs += ecoli_node_dynamic.c -srcs += ecoli_node_file.c -srcs += ecoli_node_helper.c -srcs += ecoli_node_int.c -srcs += ecoli_node_many.c -srcs += ecoli_node_none.c -srcs += ecoli_node_once.c -srcs += ecoli_node_option.c -srcs += ecoli_node_or.c -srcs += ecoli_node_re.c -srcs += ecoli_node_re_lex.c -srcs += ecoli_node_seq.c -srcs += ecoli_node_sh_lex.c -srcs += ecoli_node_space.c -srcs += ecoli_node_str.c -srcs += ecoli_node_subset.c -srcs += ecoli_parse.c -srcs += ecoli_string.c -srcs += ecoli_vec.c - -shlib-y-$(O)libecoli.so := $(srcs) - -ldflags-$(O)test = -rdynamic -exe-y-$(O)test = $(srcs) main.c - -ldflags-$(O)readline = -lreadline -ltermcap -exe-y-$(O)readline = $(srcs) main-readline.c - -ldflags-$(O)parse-yaml = -lyaml -exe-y-$(O)parse-yaml = $(srcs) parse-yaml.c - -include $(ECOLI)/mk/ecoli-post.mk - -all: _ecoli_all - -clean: _ecoli_clean - -.PHONY: clean all diff --git a/lib/ecoli_assert.c b/lib/ecoli_assert.c deleted file mode 100644 index a36d6f6..0000000 --- a/lib/ecoli_assert.c +++ /dev/null @@ -1,26 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include - -#include - -void __ec_assert_print(bool expr, const char *expr_str, const char *format, ...) -{ - va_list ap; - - if (expr) - return; - - /* LCOV_EXCL_START */ - va_start(ap, format); - fprintf(stderr, "assertion failed: '%s' is false\n", expr_str); - vfprintf(stderr, format, ap); - va_end(ap); - abort(); - /* LCOV_EXCL_END */ -} diff --git a/lib/ecoli_assert.h b/lib/ecoli_assert.h deleted file mode 100644 index fcd2186..0000000 --- a/lib/ecoli_assert.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 - -/** - * Abort if the condition is false. - * - * If expression is false this macro will prints an error message to - * standard error and terminates the program by calling abort(3). - * - * @param expr - * The expression to be checked. - * @param args - * The format string, optionally followed by other arguments. - */ -#define ec_assert_print(expr, args...) \ - __ec_assert_print(expr, #expr, args) - -/* internal */ -void __ec_assert_print(bool expr, const char *expr_str, - const char *format, ...); - -/** - * Check a condition or return. - * - * If the condition is true, do nothing. If it is false, set - * errno and return the specified value. - * - * @param cond - * The condition to test. - * @param ret - * The value to return. - * @param err - * The errno to set. - */ -#define EC_CHECK_ARG(cond, ret, err) do { \ - if (!(cond)) { \ - errno = err; \ - return ret; \ - } \ - } while(0) - -#endif diff --git a/lib/ecoli_complete.c b/lib/ecoli_complete.c deleted file mode 100644 index 7ad846c..0000000 --- a/lib/ecoli_complete.c +++ /dev/null @@ -1,765 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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="), "bad dump\n"); - testres |= EC_TEST_CHECK( - strstr(buf, "comp="), "bad dump\n"); - free(buf); - buf = NULL; - - iter = ec_comp_iter(c, EC_COMP_ALL); - item = ec_comp_iter_next(iter); - if (item == NULL) - goto fail; - - testres |= EC_TEST_CHECK( - !strcmp(ec_comp_item_get_display(item), "xx"), - "bad item display\n"); - testres |= EC_TEST_CHECK( - ec_comp_item_get_type(item) == EC_COMP_FULL, - "bad item type\n"); - testres |= EC_TEST_CHECK( - !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_x"), - "bad item node\n"); - - item = ec_comp_iter_next(iter); - if (item == NULL) - goto fail; - - testres |= EC_TEST_CHECK( - !strcmp(ec_comp_item_get_display(item), "yy"), - "bad item display\n"); - testres |= EC_TEST_CHECK( - ec_comp_item_get_type(item) == EC_COMP_FULL, - "bad item type\n"); - testres |= EC_TEST_CHECK( - !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_y"), - "bad item node\n"); - - item = ec_comp_iter_next(iter); - testres |= EC_TEST_CHECK(item == NULL, "should be the last item\n"); - - ec_comp_iter_free(iter); - ec_comp_free(c); - ec_node_free(node); - - return testres; - -fail: - ec_comp_iter_free(iter); - ec_comp_free(c); - ec_node_free(node); - if (f != NULL) - fclose(f); - free(buf); - - return -1; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_comp_test = { - .name = "comp", - .test = ec_comp_testcase, -}; - -EC_TEST_REGISTER(ec_comp_test); diff --git a/lib/ecoli_complete.h b/lib/ecoli_complete.h deleted file mode 100644 index 1ed67f0..0000000 --- a/lib/ecoli_complete.h +++ /dev/null @@ -1,234 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 -#include -#include - -struct ec_node; - -enum ec_comp_type { /* XXX should be a define */ - EC_COMP_UNKNOWN = 0x1, - EC_COMP_FULL = 0x2, - EC_COMP_PARTIAL = 0x4, - EC_COMP_ALL = 0x7, -}; - -struct ec_comp_item; - -TAILQ_HEAD(ec_comp_item_list, ec_comp_item); - -struct ec_comp_group { - TAILQ_ENTRY(ec_comp_group) next; - const struct ec_node *node; - struct ec_comp_item_list items; - struct ec_parse *state; - struct ec_keyval *attrs; -}; - -TAILQ_HEAD(ec_comp_group_list, ec_comp_group); - -struct ec_comp { - unsigned count; - unsigned count_full; - unsigned count_partial; - unsigned count_unknown; - struct ec_parse *cur_state; - struct ec_comp_group *cur_group; - struct ec_comp_group_list groups; - struct ec_keyval *attrs; -}; - -/* - * return a comp object filled with items - * return NULL on error (nomem?) - */ -struct ec_comp *ec_node_complete(const struct ec_node *node, - const char *str); -struct ec_comp *ec_node_complete_strvec(const struct ec_node *node, - const struct ec_strvec *strvec); - -/* internal: used by nodes */ -int ec_node_complete_child(const struct ec_node *node, - struct ec_comp *comp, - const struct ec_strvec *strvec); - -/** - * Create a completion object (list of completion items). - * - * - */ -struct ec_comp *ec_comp(struct ec_parse *state); - -/** - * Free a completion object and all its items. - * - * - */ -void ec_comp_free(struct ec_comp *comp); - -/** - * - * - * - */ -void ec_comp_dump(FILE *out, - const struct ec_comp *comp); - -/** - * Merge items contained in 'from' into 'to' - * - * The 'from' comp struct is freed. - */ -int ec_comp_merge(struct ec_comp *to, - struct ec_comp *from); - -struct ec_parse *ec_comp_get_state(struct ec_comp *comp); - -/* shortcut for ec_comp_item() + ec_comp_item_add() */ -int ec_comp_add_item(struct ec_comp *comp, - const struct ec_node *node, - struct ec_comp_item **p_item, - enum ec_comp_type type, - const char *start, const char *full); - -/** - * - */ -int ec_comp_item_set_str(struct ec_comp_item *item, - const char *str); - -/** - * Get the string value of a completion item. - * - * - */ -const char * -ec_comp_item_get_str(const struct ec_comp_item *item); - -/** - * Get the display string value of a completion item. - * - * - */ -const char * -ec_comp_item_get_display(const struct ec_comp_item *item); - -/** - * Get the completion string value of a completion item. - * - * - */ -const char * -ec_comp_item_get_completion(const struct ec_comp_item *item); - -/** - * Get the group of a completion item. - * - * - */ -const struct ec_comp_group * -ec_comp_item_get_grp(const struct ec_comp_item *item); - -/** - * Get the type of a completion item. - * - * - */ -enum ec_comp_type -ec_comp_item_get_type(const struct ec_comp_item *item); - -/** - * Get the node associated to a completion item. - * - * - */ -const struct ec_node * -ec_comp_item_get_node(const struct ec_comp_item *item); - -/** - * Set the display value of an item. - * - * - */ -int ec_comp_item_set_display(struct ec_comp_item *item, - const char *display); - -/** - * Set the completion value of an item. - * - * - */ -int ec_comp_item_set_completion(struct ec_comp_item *item, - const char *completion); - -/** - * - * - * - */ -int -ec_node_complete_unknown(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec); - -/** - * - * - * - */ -unsigned int ec_comp_count( - const struct ec_comp *comp, - enum ec_comp_type flags); - -/** - * - * - * - */ -struct ec_comp_iter { - enum ec_comp_type type; - struct ec_comp *comp; - struct ec_comp_group *cur_node; - struct ec_comp_item *cur_match; -}; - -/** - * - * - * - */ -struct ec_comp_iter * -ec_comp_iter(struct ec_comp *comp, - enum ec_comp_type type); - -/** - * - * - * - */ -struct ec_comp_item *ec_comp_iter_next( - struct ec_comp_iter *iter); - -/** - * - * - * - */ -void ec_comp_iter_free(struct ec_comp_iter *iter); - - -#endif diff --git a/lib/ecoli_config.c b/lib/ecoli_config.c deleted file mode 100644 index 0698f07..0000000 --- a/lib/ecoli_config.c +++ /dev/null @@ -1,1153 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(config); - -const char *ec_config_reserved_keys[] = { - "id", - "attrs", - "help", - "type", -}; - -static int -__ec_config_dump(FILE *out, const char *key, const struct ec_config *config, - size_t indent); -static int -ec_config_dict_validate(const struct ec_keyval *dict, - const struct ec_config_schema *schema); - -bool -ec_config_key_is_reserved(const char *name) -{ - size_t i; - - for (i = 0; i < EC_COUNT_OF(ec_config_reserved_keys); i++) { - if (!strcmp(name, ec_config_reserved_keys[i])) - return true; - } - return false; -} - -/* return ec_value type as a string */ -static const char * -ec_config_type_str(enum ec_config_type type) -{ - switch (type) { - case EC_CONFIG_TYPE_BOOL: return "bool"; - case EC_CONFIG_TYPE_INT64: return "int64"; - case EC_CONFIG_TYPE_UINT64: return "uint64"; - case EC_CONFIG_TYPE_STRING: return "string"; - case EC_CONFIG_TYPE_NODE: return "node"; - case EC_CONFIG_TYPE_LIST: return "list"; - case EC_CONFIG_TYPE_DICT: return "dict"; - default: return "unknown"; - } -} - -static size_t -ec_config_schema_len(const struct ec_config_schema *schema) -{ - size_t i; - - if (schema == NULL) - return 0; - for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) - ; - return i; -} - -static int -__ec_config_schema_validate(const struct ec_config_schema *schema, - enum ec_config_type type) -{ - size_t i, j; - int ret; - - if (type == EC_CONFIG_TYPE_LIST) { - if (schema[0].key != NULL) { - errno = EINVAL; - EC_LOG(EC_LOG_ERR, "list schema key must be NULL\n"); - return -1; - } - } else if (type == EC_CONFIG_TYPE_DICT) { - for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) { - if (schema[i].key == NULL) { - errno = EINVAL; - EC_LOG(EC_LOG_ERR, - "dict schema key should not be NULL\n"); - return -1; - } - } - } else { - errno = EINVAL; - EC_LOG(EC_LOG_ERR, "invalid schema type\n"); - return -1; - } - - for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) { - if (schema[i].key != NULL && - ec_config_key_is_reserved(schema[i].key)) { - errno = EINVAL; - EC_LOG(EC_LOG_ERR, - "key name <%s> is reserved\n", schema[i].key); - return -1; - } - /* check for duplicate name if more than one element */ - for (j = i + 1; schema[j].type != EC_CONFIG_TYPE_NONE; j++) { - if (!strcmp(schema[i].key, schema[j].key)) { - errno = EEXIST; - EC_LOG(EC_LOG_ERR, - "duplicate key <%s> in schema\n", - schema[i].key); - return -1; - } - } - - switch (schema[i].type) { - case EC_CONFIG_TYPE_BOOL: - case EC_CONFIG_TYPE_INT64: - case EC_CONFIG_TYPE_UINT64: - case EC_CONFIG_TYPE_STRING: - case EC_CONFIG_TYPE_NODE: - if (schema[i].subschema != NULL || ec_config_schema_len( - schema[i].subschema) != 0) { - errno = EINVAL; - EC_LOG(EC_LOG_ERR, - "key <%s> should not have subtype/subschema\n", - schema[i].key); - return -1; - } - break; - case EC_CONFIG_TYPE_LIST: - if (schema[i].subschema == NULL || ec_config_schema_len( - schema[i].subschema) != 1) { - errno = EINVAL; - EC_LOG(EC_LOG_ERR, - "key <%s> must have subschema of length 1\n", - schema[i].key); - return -1; - } - break; - case EC_CONFIG_TYPE_DICT: - if (schema[i].subschema == NULL || ec_config_schema_len( - schema[i].subschema) == 0) { - errno = EINVAL; - EC_LOG(EC_LOG_ERR, - "key <%s> must have subschema\n", - schema[i].key); - return -1; - } - break; - default: - EC_LOG(EC_LOG_ERR, "invalid type for key <%s>\n", - schema[i].key); - errno = EINVAL; - return -1; - } - - if (schema[i].subschema == NULL) - continue; - - ret = __ec_config_schema_validate(schema[i].subschema, - schema[i].type); - if (ret < 0) { - EC_LOG(EC_LOG_ERR, "cannot parse subschema %s%s\n", - schema[i].key ? "key=" : "", - schema[i].key ? : ""); - return ret; - } - } - - return 0; -} - -int -ec_config_schema_validate(const struct ec_config_schema *schema) -{ - return __ec_config_schema_validate(schema, EC_CONFIG_TYPE_DICT); -} - -static void -__ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema, - size_t indent) -{ - size_t i; - - for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) { - fprintf(out, "%*s" "%s%s%stype=%s desc='%s'\n", - (int)indent * 4, "", - schema[i].key ? "key=": "", - schema[i].key ? : "", - schema[i].key ? " ": "", - ec_config_type_str(schema[i].type), - schema[i].desc); - if (schema[i].subschema == NULL) - continue; - __ec_config_schema_dump(out, schema[i].subschema, - indent + 1); - } -} - -void -ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema) -{ - fprintf(out, "------------------- schema dump:\n"); - - if (schema == NULL) { - fprintf(out, "no schema\n"); - return; - } - - __ec_config_schema_dump(out, schema, 0); -} - -enum ec_config_type ec_config_get_type(const struct ec_config *config) -{ - return config->type; -} - -struct ec_config * -ec_config_bool(bool boolean) -{ - struct ec_config *value = NULL; - - value = ec_calloc(1, sizeof(*value)); - if (value == NULL) - return NULL; - - value->type = EC_CONFIG_TYPE_BOOL; - value->boolean = boolean; - - return value; -} - -struct ec_config * -ec_config_i64(int64_t i64) -{ - struct ec_config *value = NULL; - - value = ec_calloc(1, sizeof(*value)); - if (value == NULL) - return NULL; - - value->type = EC_CONFIG_TYPE_INT64; - value->i64 = i64; - - return value; -} - -struct ec_config * -ec_config_u64(uint64_t u64) -{ - struct ec_config *value = NULL; - - value = ec_calloc(1, sizeof(*value)); - if (value == NULL) - return NULL; - - value->type = EC_CONFIG_TYPE_UINT64; - value->u64 = u64; - - return value; -} - -/* duplicate string */ -struct ec_config * -ec_config_string(const char *string) -{ - struct ec_config *value = NULL; - char *s = NULL; - - if (string == NULL) - goto fail; - - s = ec_strdup(string); - if (s == NULL) - goto fail; - - value = ec_calloc(1, sizeof(*value)); - if (value == NULL) - goto fail; - - value->type = EC_CONFIG_TYPE_STRING; - value->string = s; - - return value; - -fail: - ec_free(value); - ec_free(s); - return NULL; -} - -/* "consume" the node */ -struct ec_config * -ec_config_node(struct ec_node *node) -{ - struct ec_config *value = NULL; - - if (node == NULL) - goto fail; - - value = ec_calloc(1, sizeof(*value)); - if (value == NULL) - goto fail; - - value->type = EC_CONFIG_TYPE_NODE; - value->node = node; - - return value; - -fail: - ec_node_free(node); - ec_free(value); - return NULL; -} - -struct ec_config * -ec_config_dict(void) -{ - struct ec_config *value = NULL; - struct ec_keyval *dict = NULL; - - dict = ec_keyval(); - if (dict == NULL) - goto fail; - - value = ec_calloc(1, sizeof(*value)); - if (value == NULL) - goto fail; - - value->type = EC_CONFIG_TYPE_DICT; - value->dict = dict; - - return value; - -fail: - ec_keyval_free(dict); - ec_free(value); - return NULL; -} - -struct ec_config * -ec_config_list(void) -{ - struct ec_config *value = NULL; - - value = ec_calloc(1, sizeof(*value)); - if (value == NULL) - return NULL; - - value->type = EC_CONFIG_TYPE_LIST; - TAILQ_INIT(&value->list); - - return value; -} - -const struct ec_config_schema * -ec_config_schema_lookup(const struct ec_config_schema *schema, - const char *key) -{ - size_t i; - - for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) { - if (!strcmp(key, schema[i].key)) - return &schema[i]; - } - - errno = ENOENT; - return NULL; -} - -enum ec_config_type -ec_config_schema_type(const struct ec_config_schema *schema_elt) -{ - return schema_elt->type; -} - -const struct ec_config_schema * -ec_config_schema_sub(const struct ec_config_schema *schema_elt) -{ - return schema_elt->subschema; -} - -void -ec_config_free(struct ec_config *value) -{ - if (value == NULL) - return; - - switch (value->type) { - case EC_CONFIG_TYPE_STRING: - ec_free(value->string); - break; - case EC_CONFIG_TYPE_NODE: - ec_node_free(value->node); - break; - case EC_CONFIG_TYPE_LIST: - while (!TAILQ_EMPTY(&value->list)) { - struct ec_config *v; - v = TAILQ_FIRST(&value->list); - TAILQ_REMOVE(&value->list, v, next); - ec_config_free(v); - } - break; - case EC_CONFIG_TYPE_DICT: - ec_keyval_free(value->dict); - break; - default: - break; - } - - ec_free(value); -} - -static int -ec_config_list_cmp(const struct ec_config_list *list1, - const struct ec_config_list *list2) -{ - const struct ec_config *v1, *v2; - - for (v1 = TAILQ_FIRST(list1), v2 = TAILQ_FIRST(list2); - v1 != NULL && v2 != NULL; - v1 = TAILQ_NEXT(v1, next), v2 = TAILQ_NEXT(v2, next)) { - if (ec_config_cmp(v1, v2)) - return -1; - } - if (v1 != NULL || v2 != NULL) - return -1; - - return 0; -} - -/* XXX -> ec_keyval_cmp() */ -static int -ec_config_dict_cmp(const struct ec_keyval *d1, - const struct ec_keyval *d2) -{ - const struct ec_config *v1, *v2; - struct ec_keyval_iter *iter = NULL; - const char *key; - - if (ec_keyval_len(d1) != ec_keyval_len(d2)) - return -1; - - for (iter = ec_keyval_iter(d1); - ec_keyval_iter_valid(iter); - ec_keyval_iter_next(iter)) { - key = ec_keyval_iter_get_key(iter); - v1 = ec_keyval_iter_get_val(iter); - v2 = ec_keyval_get(d2, key); - - if (ec_config_cmp(v1, v2)) - goto fail; - } - - ec_keyval_iter_free(iter); - return 0; - -fail: - ec_keyval_iter_free(iter); - return -1; -} - -int -ec_config_cmp(const struct ec_config *value1, - const struct ec_config *value2) -{ - if (value1 == NULL || value2 == NULL) { - errno = EINVAL; - return -1; - } - - if (value1->type != value2->type) - return -1; - - switch (value1->type) { - case EC_CONFIG_TYPE_BOOL: - if (value1->boolean == value2->boolean) - return 0; - case EC_CONFIG_TYPE_INT64: - if (value1->i64 == value2->i64) - return 0; - case EC_CONFIG_TYPE_UINT64: - if (value1->u64 == value2->u64) - return 0; - case EC_CONFIG_TYPE_STRING: - if (!strcmp(value1->string, value2->string)) - return 0; - case EC_CONFIG_TYPE_NODE: - if (value1->node == value2->node) - return 0; - case EC_CONFIG_TYPE_LIST: - return ec_config_list_cmp(&value1->list, &value2->list); - case EC_CONFIG_TYPE_DICT: - return ec_config_dict_cmp(value1->dict, value2->dict); - default: - break; - } - - return -1; -} - -static int -ec_config_list_validate(const struct ec_config_list *list, - const struct ec_config_schema *sch) -{ - const struct ec_config *value; - - TAILQ_FOREACH(value, list, next) { - if (value->type != sch->type) { - errno = EBADMSG; - return -1; - } - - if (value->type == EC_CONFIG_TYPE_LIST) { - if (ec_config_list_validate(&value->list, - sch->subschema) < 0) - return -1; - } else if (value->type == EC_CONFIG_TYPE_DICT) { - if (ec_config_dict_validate(value->dict, - sch->subschema) < 0) - return -1; - } - } - - return 0; -} - -static int -ec_config_dict_validate(const struct ec_keyval *dict, - const struct ec_config_schema *schema) -{ - const struct ec_config *value; - struct ec_keyval_iter *iter = NULL; - const struct ec_config_schema *sch; - const char *key; - - for (iter = ec_keyval_iter(dict); - ec_keyval_iter_valid(iter); - ec_keyval_iter_next(iter)) { - - key = ec_keyval_iter_get_key(iter); - value = ec_keyval_iter_get_val(iter); - sch = ec_config_schema_lookup(schema, key); - if (sch == NULL || sch->type != value->type) { - errno = EBADMSG; - goto fail; - } - if (value->type == EC_CONFIG_TYPE_LIST) { - if (ec_config_list_validate(&value->list, - sch->subschema) < 0) - goto fail; - } else if (value->type == EC_CONFIG_TYPE_DICT) { - if (ec_config_dict_validate(value->dict, - sch->subschema) < 0) - goto fail; - } - } - - ec_keyval_iter_free(iter); - return 0; - -fail: - ec_keyval_iter_free(iter); - return -1; -} - -int -ec_config_validate(const struct ec_config *dict, - const struct ec_config_schema *schema) -{ - if (dict->type != EC_CONFIG_TYPE_DICT || schema == NULL) { - errno = EINVAL; - goto fail; - } - - if (ec_config_dict_validate(dict->dict, schema) < 0) - goto fail; - - return 0 -; -fail: - return -1; -} - -struct ec_config * -ec_config_dict_get(const struct ec_config *config, const char *key) -{ - if (config == NULL) { - errno = EINVAL; - return NULL; - } - - if (config->type != EC_CONFIG_TYPE_DICT) { - errno = EINVAL; - return NULL; - } - - return ec_keyval_get(config->dict, key); -} - -struct ec_config * -ec_config_list_first(struct ec_config *list) -{ - if (list == NULL || list->type != EC_CONFIG_TYPE_LIST) { - errno = EINVAL; - return NULL; - } - - return TAILQ_FIRST(&list->list); -} - -struct ec_config * -ec_config_list_next(struct ec_config *list, struct ec_config *config) -{ - (void)list; - return TAILQ_NEXT(config, next); -} - -/* value is consumed */ -int ec_config_dict_set(struct ec_config *config, const char *key, - struct ec_config *value) -{ - void (*free_cb)(struct ec_config *) = ec_config_free; - - if (config == NULL || key == NULL || value == NULL) { - errno = EINVAL; - goto fail; - } - if (config->type != EC_CONFIG_TYPE_DICT) { - errno = EINVAL; - goto fail; - } - - return ec_keyval_set(config->dict, key, value, - (void (*)(void *))free_cb); - -fail: - ec_config_free(value); - return -1; -} - -int ec_config_dict_del(struct ec_config *config, const char *key) -{ - if (config == NULL || key == NULL) { - errno = EINVAL; - return -1; - } - if (config->type != EC_CONFIG_TYPE_DICT) { - errno = EINVAL; - return -1; - } - - return ec_keyval_del(config->dict, key); -} - -/* value is consumed */ -int -ec_config_list_add(struct ec_config *list, - struct ec_config *value) -{ - if (list == NULL || list->type != EC_CONFIG_TYPE_LIST || value == NULL) { - errno = EINVAL; - goto fail; - } - - TAILQ_INSERT_TAIL(&list->list, value, next); - - return 0; - -fail: - ec_config_free(value); - return -1; -} - -int ec_config_list_del(struct ec_config *list, struct ec_config *config) -{ - if (list == NULL || list->type != EC_CONFIG_TYPE_LIST) { - errno = EINVAL; - return -1; - } - - TAILQ_REMOVE(&list->list, config, next); - ec_config_free(config); - return 0; -} - -static struct ec_config * -ec_config_list_dup(const struct ec_config_list *list) -{ - struct ec_config *dup = NULL, *v, *value; - - dup = ec_config_list(); - if (dup == NULL) - goto fail; - - TAILQ_FOREACH(v, list, next) { - value = ec_config_dup(v); - if (value == NULL) - goto fail; - if (ec_config_list_add(dup, value) < 0) - goto fail; - } - - return dup; - -fail: - ec_config_free(dup); - return NULL; -} - -static struct ec_config * -ec_config_dict_dup(const struct ec_keyval *dict) -{ - struct ec_config *dup = NULL, *value; - struct ec_keyval_iter *iter = NULL; - const char *key; - - dup = ec_config_dict(); - if (dup == NULL) - goto fail; - - for (iter = ec_keyval_iter(dict); - ec_keyval_iter_valid(iter); - ec_keyval_iter_next(iter)) { - key = ec_keyval_iter_get_key(iter); - value = ec_config_dup(ec_keyval_iter_get_val(iter)); - if (value == NULL) - goto fail; - if (ec_config_dict_set(dup, key, value) < 0) - goto fail; - } - ec_keyval_iter_free(iter); - - return dup; - -fail: - ec_config_free(dup); - ec_keyval_iter_free(iter); - return NULL; -} - -struct ec_config * -ec_config_dup(const struct ec_config *config) -{ - if (config == NULL) { - errno = EINVAL; - return NULL; - } - - switch (config->type) { - case EC_CONFIG_TYPE_BOOL: - return ec_config_bool(config->boolean); - case EC_CONFIG_TYPE_INT64: - return ec_config_i64(config->i64); - case EC_CONFIG_TYPE_UINT64: - return ec_config_u64(config->u64); - case EC_CONFIG_TYPE_STRING: - return ec_config_string(config->string); - case EC_CONFIG_TYPE_NODE: - return ec_config_node(ec_node_clone(config->node)); - case EC_CONFIG_TYPE_LIST: - return ec_config_list_dup(&config->list); - case EC_CONFIG_TYPE_DICT: - return ec_config_dict_dup(config->dict); - default: - errno = EINVAL; - break; - } - - return NULL; -} - -static int -ec_config_list_dump(FILE *out, const char *key, - const struct ec_config_list *list, size_t indent) -{ - const struct ec_config *v; - - fprintf(out, "%*s" "%s%s%stype=list\n", (int)indent * 4, "", - key ? "key=": "", - key ? key: "", - key ? " ": ""); - - TAILQ_FOREACH(v, list, next) { - if (__ec_config_dump(out, NULL, v, indent + 1) < 0) - return -1; - } - - return 0; -} - -static int -ec_config_dict_dump(FILE *out, const char *key, const struct ec_keyval *dict, - size_t indent) -{ - const struct ec_config *value; - struct ec_keyval_iter *iter; - const char *k; - - fprintf(out, "%*s" "%s%s%stype=dict\n", (int)indent * 4, "", - key ? "key=": "", - key ? key: "", - key ? " ": ""); - - for (iter = ec_keyval_iter(dict); - ec_keyval_iter_valid(iter); - ec_keyval_iter_next(iter)) { - k = ec_keyval_iter_get_key(iter); - value = ec_keyval_iter_get_val(iter); - if (__ec_config_dump(out, k, value, indent + 1) < 0) - goto fail; - } - ec_keyval_iter_free(iter); - return 0; - -fail: - ec_keyval_iter_free(iter); - return -1; -} - -static int -__ec_config_dump(FILE *out, const char *key, const struct ec_config *value, - size_t indent) -{ - char *val_str = NULL; - - switch (value->type) { - case EC_CONFIG_TYPE_BOOL: - if (value->boolean) - ec_asprintf(&val_str, "true"); - else - ec_asprintf(&val_str, "false"); - break; - case EC_CONFIG_TYPE_INT64: - ec_asprintf(&val_str, "%"PRIu64, value->u64); - break; - case EC_CONFIG_TYPE_UINT64: - ec_asprintf(&val_str, "%"PRIi64, value->i64); - break; - case EC_CONFIG_TYPE_STRING: - ec_asprintf(&val_str, "%s", value->string); - break; - case EC_CONFIG_TYPE_NODE: - ec_asprintf(&val_str, "%p", value->node); - break; - case EC_CONFIG_TYPE_LIST: - return ec_config_list_dump(out, key, &value->list, indent); - case EC_CONFIG_TYPE_DICT: - return ec_config_dict_dump(out, key, value->dict, indent); - default: - errno = EINVAL; - break; - } - - /* errno is already set on error */ - if (val_str == NULL) - goto fail; - - fprintf(out, "%*s" "%s%s%stype=%s val=%s\n", (int)indent * 4, "", - key ? "key=": "", - key ? key: "", - key ? " ": "", - ec_config_type_str(value->type), val_str); - - ec_free(val_str); - return 0; - -fail: - ec_free(val_str); - return -1; -} - -void -ec_config_dump(FILE *out, const struct ec_config *config) -{ - fprintf(out, "------------------- config dump:\n"); - - if (config == NULL) { - fprintf(out, "no config\n"); - return; - } - - if (__ec_config_dump(out, NULL, config, 0) < 0) - fprintf(out, "error while dumping\n"); -} - -/* LCOV_EXCL_START */ -static const struct ec_config_schema sch_intlist_elt[] = { - { - .desc = "This is a description for int", - .type = EC_CONFIG_TYPE_INT64, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static const struct ec_config_schema sch_dict[] = { - { - .key = "my_int", - .desc = "This is a description for int", - .type = EC_CONFIG_TYPE_INT64, - }, - { - .key = "my_int2", - .desc = "This is a description for int2", - .type = EC_CONFIG_TYPE_INT64, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static const struct ec_config_schema sch_dictlist_elt[] = { - { - .desc = "This is a description for dict", - .type = EC_CONFIG_TYPE_DICT, - .subschema = sch_dict, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static const struct ec_config_schema sch_baseconfig[] = { - { - .key = "my_bool", - .desc = "This is a description for bool", - .type = EC_CONFIG_TYPE_BOOL, - }, - { - .key = "my_int", - .desc = "This is a description for int", - .type = EC_CONFIG_TYPE_INT64, - }, - { - .key = "my_string", - .desc = "This is a description for string", - .type = EC_CONFIG_TYPE_STRING, - }, - { - .key = "my_node", - .desc = "This is a description for node", - .type = EC_CONFIG_TYPE_NODE, - }, - { - .key = "my_intlist", - .desc = "This is a description for list", - .type = EC_CONFIG_TYPE_LIST, - .subschema = sch_intlist_elt, - }, - { - .key = "my_dictlist", - .desc = "This is a description for list", - .type = EC_CONFIG_TYPE_LIST, - .subschema = sch_dictlist_elt, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_config_testcase(void) -{ - struct ec_node *node = NULL; - struct ec_keyval *dict = NULL; - const struct ec_config *value = NULL; - struct ec_config *config = NULL, *config2 = NULL; - struct ec_config *list = NULL, *subconfig = NULL; - struct ec_config *list_, *config_; - int testres = 0; - int ret; - - testres |= EC_TEST_CHECK(ec_config_key_is_reserved("id"), - "'id' should be reserved"); - testres |= EC_TEST_CHECK(!ec_config_key_is_reserved("foo"), - "'foo' should not be reserved"); - - node = ec_node("empty", EC_NO_ID); - if (node == NULL) - goto fail; - - if (ec_config_schema_validate(sch_baseconfig) < 0) { - EC_LOG(EC_LOG_ERR, "invalid config schema\n"); - goto fail; - } - - ec_config_schema_dump(stdout, sch_baseconfig); - - config = ec_config_dict(); - if (config == NULL) - goto fail; - - ret = ec_config_dict_set(config, "my_bool", ec_config_bool(true)); - testres |= EC_TEST_CHECK(ret == 0, "cannot set boolean"); - value = ec_config_dict_get(config, "my_bool"); - testres |= EC_TEST_CHECK( - value != NULL && - value->type == EC_CONFIG_TYPE_BOOL && - value->boolean == true, - "unexpected boolean value"); - - ret = ec_config_dict_set(config, "my_int", ec_config_i64(1234)); - testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); - value = ec_config_dict_get(config, "my_int"); - testres |= EC_TEST_CHECK( - value != NULL && - value->type == EC_CONFIG_TYPE_INT64 && - value->i64 == 1234, - "unexpected int value"); - - testres |= EC_TEST_CHECK( - ec_config_validate(config, sch_baseconfig) == 0, - "cannot validate config\n"); - - ret = ec_config_dict_set(config, "my_string", ec_config_string("toto")); - testres |= EC_TEST_CHECK(ret == 0, "cannot set string"); - value = ec_config_dict_get(config, "my_string"); - testres |= EC_TEST_CHECK( - value != NULL && - value->type == EC_CONFIG_TYPE_STRING && - !strcmp(value->string, "toto"), - "unexpected string value"); - - list = ec_config_list(); - if (list == NULL) - goto fail; - - subconfig = ec_config_dict(); - if (subconfig == NULL) - goto fail; - - ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(1)); - testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); - value = ec_config_dict_get(subconfig, "my_int"); - testres |= EC_TEST_CHECK( - value != NULL && - value->type == EC_CONFIG_TYPE_INT64 && - value->i64 == 1, - "unexpected int value"); - - ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(2)); - testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); - value = ec_config_dict_get(subconfig, "my_int2"); - testres |= EC_TEST_CHECK( - value != NULL && - value->type == EC_CONFIG_TYPE_INT64 && - value->i64 == 2, - "unexpected int value"); - - testres |= EC_TEST_CHECK( - ec_config_validate(subconfig, sch_dict) == 0, - "cannot validate subconfig\n"); - - ret = ec_config_list_add(list, subconfig); - subconfig = NULL; /* freed */ - testres |= EC_TEST_CHECK(ret == 0, "cannot add in list"); - - subconfig = ec_config_dict(); - if (subconfig == NULL) - goto fail; - - ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(3)); - testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); - value = ec_config_dict_get(subconfig, "my_int"); - testres |= EC_TEST_CHECK( - value != NULL && - value->type == EC_CONFIG_TYPE_INT64 && - value->i64 == 3, - "unexpected int value"); - - ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(4)); - testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); - value = ec_config_dict_get(subconfig, "my_int2"); - testres |= EC_TEST_CHECK( - value != NULL && - value->type == EC_CONFIG_TYPE_INT64 && - value->i64 == 4, - "unexpected int value"); - - testres |= EC_TEST_CHECK( - ec_config_validate(subconfig, sch_dict) == 0, - "cannot validate subconfig\n"); - - ret = ec_config_list_add(list, subconfig); - subconfig = NULL; /* freed */ - testres |= EC_TEST_CHECK(ret == 0, "cannot add in list"); - - ret = ec_config_dict_set(config, "my_dictlist", list); - list = NULL; - testres |= EC_TEST_CHECK(ret == 0, "cannot set list"); - - testres |= EC_TEST_CHECK( - ec_config_validate(config, sch_baseconfig) == 0, - "cannot validate config\n"); - - list_ = ec_config_dict_get(config, "my_dictlist"); - for (config_ = ec_config_list_first(list_); config_ != NULL; - config_ = ec_config_list_next(list_, config_)) { - ec_config_dump(stdout, config_); - } - - ec_config_dump(stdout, config); - - config2 = ec_config_dup(config); - testres |= EC_TEST_CHECK(config2 != NULL, "cannot duplicate config"); - testres |= EC_TEST_CHECK( - ec_config_cmp(config, config2) == 0, - "fail to compare config"); - ec_config_free(config2); - config2 = NULL; - - /* remove the first element */ - ec_config_list_del(list_, ec_config_list_first(list_)); - testres |= EC_TEST_CHECK( - ec_config_validate(config, sch_baseconfig) == 0, - "cannot validate config\n"); - - ec_config_dump(stdout, config); - - ec_config_free(list); - ec_config_free(subconfig); - ec_config_free(config); - ec_keyval_free(dict); - ec_node_free(node); - - return testres; - -fail: - ec_config_free(list); - ec_config_free(subconfig); - ec_config_free(config); - ec_config_free(config2); - ec_keyval_free(dict); - ec_node_free(node); - - return -1; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_config_test = { - .name = "config", - .test = ec_config_testcase, -}; - -EC_TEST_REGISTER(ec_config_test); diff --git a/lib/ecoli_config.h b/lib/ecoli_config.h deleted file mode 100644 index 9d7c628..0000000 --- a/lib/ecoli_config.h +++ /dev/null @@ -1,392 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -#ifndef ECOLI_CONFIG_ -#define ECOLI_CONFIG_ - -#include -#include -#include -#include - -#ifndef EC_COUNT_OF //XXX -#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \ - ((size_t)(!(sizeof(x) % sizeof(0[x]))))) -#endif - -struct ec_config; -struct ec_keyval; - -/** - * The type identifier for a config value. - */ -enum ec_config_type { - EC_CONFIG_TYPE_NONE = 0, - EC_CONFIG_TYPE_BOOL, - EC_CONFIG_TYPE_INT64, - EC_CONFIG_TYPE_UINT64, - EC_CONFIG_TYPE_STRING, - EC_CONFIG_TYPE_NODE, - EC_CONFIG_TYPE_LIST, - EC_CONFIG_TYPE_DICT, -}; - -/** - * Structure describing the format of a configuration value. - * - * This structure is used in a const array which is referenced by a - * struct ec_config. Each entry of the array represents a key/value - * storage of the configuration dictionary. - */ -struct ec_config_schema { - const char *key; /**< The key string (NULL for list elts). */ - const char *desc; /**< A description of the value. */ - enum ec_config_type type; /**< Type of the value */ - - /** If type is dict or list, the schema of the dict or list - * elements. Else must be NULL. */ - const struct ec_config_schema *subschema; -}; - -TAILQ_HEAD(ec_config_list, ec_config); - -/** - * Structure storing the configuration data. - */ -struct ec_config { - /** type of value stored in the union */ - enum ec_config_type type; - - union { - bool boolean; /** Boolean value */ - int64_t i64; /** Signed integer value */ - uint64_t u64; /** Unsigned integer value */ - char *string; /** String value */ - struct ec_node *node; /** Node value */ - struct ec_keyval *dict; /** Hash table value */ - struct ec_config_list list; /** List value */ - }; - - /** - * Next in list, only valid if type is list. - */ - TAILQ_ENTRY(ec_config) next; -}; - -/* schema */ - -/** - * Validate a configuration schema array. - * - * @param schema - * Pointer to the first element of the schema array. The array - * must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE). - * @return - * 0 if the schema is valid, or -1 on error (errno is set). - */ -int ec_config_schema_validate(const struct ec_config_schema *schema); - -/** - * Dump a configuration schema array. - * - * @param out - * Output stream on which the dump will be sent. - * @param schema - * Pointer to the first element of the schema array. The array - * must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE). - */ -void ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema); - -/** - * Find a schema entry matching the key. - * - * Browse the schema array and lookup for the given key. - * - * @param schema - * Pointer to the first element of the schema array. The array - * must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE). - * @return - * The schema entry if it matches a key, or NULL if not found. - */ -const struct ec_config_schema * -ec_config_schema_lookup(const struct ec_config_schema *schema, - const char *key); - -/** - * Get the type of a schema entry. - * - * @param schema_elt - * Pointer to an element of the schema array. - * @return - * The type of the schema entry. - */ -enum ec_config_type -ec_config_schema_type(const struct ec_config_schema *schema_elt); - -/** - * Get the subschema of a schema entry. - * - * @param schema_elt - * Pointer to an element of the schema array. - * @return - * The subschema if any, or NULL. - */ -const struct ec_config_schema * -ec_config_schema_sub(const struct ec_config_schema *schema_elt); - -/** - * Check if a key name is reserved in a config dict. - * - * Some key names are reserved and should not be used in configs. - * - * @param name - * The name of the key to test. - * @return - * True if the key name is reserved and must not be used, else false. - */ -bool ec_config_key_is_reserved(const char *name); - -/** - * Array of reserved key names. - */ -extern const char *ec_config_reserved_keys[]; - - -/* config */ - -/** - * Get the type of the configuration. - * - * @param config - * The configuration. - * @return - * The configuration type. - */ -enum ec_config_type ec_config_get_type(const struct ec_config *config); - -/** - * Create a boolean configuration value. - * - * @param boolean - * The boolean value to be set. - * @return - * The configuration object, or NULL on error (errno is set). - */ -struct ec_config *ec_config_bool(bool boolean); - -/** - * Create a signed integer configuration value. - * - * @param i64 - * The signed integer value to be set. - * @return - * The configuration object, or NULL on error (errno is set). - */ -struct ec_config *ec_config_i64(int64_t i64); - -/** - * Create an unsigned configuration value. - * - * @param u64 - * The unsigned integer value to be set. - * @return - * The configuration object, or NULL on error (errno is set). - */ -struct ec_config *ec_config_u64(uint64_t u64); - -/** - * Create a string configuration value. - * - * @param string - * The string value to be set. The string is copied into the - * configuration object. - * @return - * The configuration object, or NULL on error (errno is set). - */ -struct ec_config *ec_config_string(const char *string); - -/** - * Create a node configuration value. - * - * @param node - * The node pointer to be set. The node is "consumed" by - * the function and should not be used by the caller, even - * on error. The caller can use ec_node_clone() to keep a - * reference on the node. - * @return - * The configuration object, or NULL on error (errno is set). - */ -struct ec_config *ec_config_node(struct ec_node *node); - -/** - * Create a hash table configuration value. - * - * @return - * A configuration object containing an empty hash table, or NULL on - * error (errno is set). - */ -struct ec_config *ec_config_dict(void); - -/** - * Create a list configuration value. - * - * @return - * The configuration object containing an empty list, or NULL on - * error (errno is set). - */ -struct ec_config *ec_config_list(void); - -/** - * Add a config object into a list. - * - * @param list - * The list configuration in which the value will be added. - * @param value - * The value configuration to add in the list. The value object - * will be freed when freeing the list object. On error, the - * value object is also freed. - * @return - * 0 on success, else -1 (errno is set). - */ -int ec_config_list_add(struct ec_config *list, struct ec_config *value); - -/** - * Remove an element from a list. - * - * The element is freed and should not be accessed. - * - * @param list - * The list configuration. - * @param config - * The element to remove from the list. - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_config_list_del(struct ec_config *list, struct ec_config *config); - -/** - * Validate a configuration. - * - * @param dict - * A hash table configuration to validate. - * @param schema - * Pointer to the first element of the schema array. The array - * must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE). - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_config_validate(const struct ec_config *dict, - const struct ec_config_schema *schema); - -/** - * Set a value in a hash table configuration - * - * @param dict - * A hash table configuration to validate. - * @param key - * The key to update. - * @param value - * The value to set. The value object will be freed when freeing the - * dict object. On error, the value object is also freed. - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_config_dict_set(struct ec_config *dict, const char *key, - struct ec_config *value); - -/** - * Remove an element from a hash table configuration. - * - * The element is freed and should not be accessed. - * - * @param dict - * A hash table configuration to validate. - * @param key - * The key of the configuration to delete. - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_config_dict_del(struct ec_config *config, const char *key); - -/** - * Compare two configurations. - */ -int ec_config_cmp(const struct ec_config *config1, - const struct ec_config *config2); - -/** - * Get configuration value. - */ -struct ec_config *ec_config_dict_get(const struct ec_config *config, - const char *key); - -/** - * Get the first element of a list. - * - * Example of use: - * for (config = ec_config_list_iter(list); - * config != NULL; - * config = ec_config_list_next(list, config)) { - * ... - * } - * - * @param list - * The list configuration to iterate. - * @return - * The first configuration element, or NULL on error (errno is set). - */ -struct ec_config *ec_config_list_first(struct ec_config *list); - -/** - * Get next element in list. - * - * @param list - * The list configuration beeing iterated. - * @param config - * The current configuration element. - * @return - * The next configuration element, or NULL if there is no more element. - */ -struct ec_config * -ec_config_list_next(struct ec_config *list, struct ec_config *config); - -/** - * Free a configuration. - * - * @param config - * The element to free. - */ -void ec_config_free(struct ec_config *config); - -/** - * Compare two configurations. - * - * @return - * 0 if the configurations are equal, else -1. - */ -int ec_config_cmp(const struct ec_config *value1, - const struct ec_config *value2); - -/** - * Duplicate a configuration. - * - * @param config - * The configuration to duplicate. - * @return - * The duplicated configuration, or NULL on error (errno is set). - */ -struct ec_config * -ec_config_dup(const struct ec_config *config); - -/** - * Dump a configuration. - * - * @param out - * Output stream on which the dump will be sent. - * @param config - * The configuration to dump. - */ -void ec_config_dump(FILE *out, const struct ec_config *config); - -#endif diff --git a/lib/ecoli_init.c b/lib/ecoli_init.c deleted file mode 100644 index fd5c0c3..0000000 --- a/lib/ecoli_init.c +++ /dev/null @@ -1,46 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include - -#include - -static struct ec_init_list init_list = TAILQ_HEAD_INITIALIZER(init_list); - -/* register an init function */ -void ec_init_register(struct ec_init *init) -{ - struct ec_init *cur; - - if (TAILQ_EMPTY(&init_list)) { - TAILQ_INSERT_HEAD(&init_list, init, next); - return; - } - - - TAILQ_FOREACH(cur, &init_list, next) { - if (init->priority > cur->priority) - continue; - - TAILQ_INSERT_BEFORE(cur, init, next); - return; - } - - TAILQ_INSERT_TAIL(&init_list, init, next); -} - -int ec_init(void) -{ - struct ec_init *init; - - TAILQ_FOREACH(init, &init_list, next) { - if (init->init() < 0) - return -1; - } - - return 0; -} diff --git a/lib/ecoli_init.h b/lib/ecoli_init.h deleted file mode 100644 index 4e8bc1e..0000000 --- a/lib/ecoli_init.h +++ /dev/null @@ -1,60 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * Register initialization routines. - */ - -#ifndef ECOLI_INIT_ -#define ECOLI_INIT_ - -#include - -#include -#include - -#define EC_INIT_REGISTER(t) \ - static void ec_init_init_##t(void); \ - static void __attribute__((constructor, used)) \ - ec_init_init_##t(void) \ - { \ - ec_init_register(&t); \ - } - -/** - * Type of init function. Return 0 on success, -1 on error. - */ -typedef int (ec_init_t)(void); - -TAILQ_HEAD(ec_init_list, ec_init); - -/** - * A structure describing a test case. - */ -struct ec_init { - TAILQ_ENTRY(ec_init) next; /**< Next in list. */ - ec_init_t *init; /**< Init function. */ - unsigned int priority; /**< Priority (0=first, 99=last) */ -}; - -/** - * Register an initialization function. - * - * @param init - * A pointer to a ec_init structure to be registered. - */ -void ec_init_register(struct ec_init *test); - -/** - * Initialize ecoli library - * - * Must be called before any other function from libecoli, except - * ec_malloc_register(). - * - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_init(void); - -#endif diff --git a/lib/ecoli_keyval.c b/lib/ecoli_keyval.c deleted file mode 100644 index 12fe66b..0000000 --- a/lib/ecoli_keyval.c +++ /dev/null @@ -1,569 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#define FACTOR 3 - -EC_LOG_TYPE_REGISTER(keyval); - -static uint32_t ec_keyval_seed; - -struct ec_keyval_elt { - char *key; - void *val; - uint32_t hash; - ec_keyval_elt_free_t free; - unsigned int refcount; -}; - -struct ec_keyval_elt_ref { - LIST_ENTRY(ec_keyval_elt_ref) next; - struct ec_keyval_elt *elt; -}; - -LIST_HEAD(ec_keyval_elt_ref_list, ec_keyval_elt_ref); - -struct ec_keyval { - size_t len; - size_t table_size; - struct ec_keyval_elt_ref_list *table; -}; - -struct ec_keyval_iter { - const struct ec_keyval *keyval; - size_t cur_idx; - const struct ec_keyval_elt_ref *cur_ref; -}; - -struct ec_keyval *ec_keyval(void) -{ - struct ec_keyval *keyval; - - keyval = ec_calloc(1, sizeof(*keyval)); - if (keyval == NULL) - return NULL; - - return keyval; -} - -static struct ec_keyval_elt_ref * -ec_keyval_lookup(const struct ec_keyval *keyval, const char *key) -{ - struct ec_keyval_elt_ref *ref; - uint32_t h, mask = keyval->table_size - 1; - - if (keyval == NULL || key == NULL) { - errno = EINVAL; - return NULL; - } - if (keyval->table_size == 0) { - errno = ENOENT; - return NULL; - } - - h = ec_murmurhash3(key, strlen(key), ec_keyval_seed); - LIST_FOREACH(ref, &keyval->table[h & mask], next) { - if (strcmp(ref->elt->key, key) == 0) - return ref; - } - - errno = ENOENT; - return NULL; -} - -static void ec_keyval_elt_ref_free(struct ec_keyval_elt_ref *ref) -{ - struct ec_keyval_elt *elt; - - if (ref == NULL) - return; - - elt = ref->elt; - if (elt != NULL && --elt->refcount == 0) { - ec_free(elt->key); - if (elt->free != NULL) - elt->free(elt->val); - ec_free(elt); - } - ec_free(ref); -} - -bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key) -{ - return !!ec_keyval_lookup(keyval, key); -} - -void *ec_keyval_get(const struct ec_keyval *keyval, const char *key) -{ - struct ec_keyval_elt_ref *ref; - - ref = ec_keyval_lookup(keyval, key); - if (ref == NULL) - return NULL; - - return ref->elt->val; -} - -int ec_keyval_del(struct ec_keyval *keyval, const char *key) -{ - struct ec_keyval_elt_ref *ref; - - ref = ec_keyval_lookup(keyval, key); - if (ref == NULL) - return -1; - - /* we could resize table here */ - - LIST_REMOVE(ref, next); - ec_keyval_elt_ref_free(ref); - keyval->len--; - - return 0; -} - -static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size) -{ - struct ec_keyval_elt_ref_list *new_table; - struct ec_keyval_elt_ref *ref; - size_t i; - - if (new_size == 0 || (new_size & (new_size - 1))) { - errno = EINVAL; - return -1; - } - - new_table = ec_calloc(new_size, sizeof(*keyval->table)); - if (new_table == NULL) - return -1; - - for (i = 0; i < keyval->table_size; i++) { - while (!LIST_EMPTY(&keyval->table[i])) { - ref = LIST_FIRST(&keyval->table[i]); - LIST_REMOVE(ref, next); - LIST_INSERT_HEAD( - &new_table[ref->elt->hash & (new_size - 1)], - ref, next); - } - } - - ec_free(keyval->table); - keyval->table = new_table; - keyval->table_size = new_size; - - return 0; -} - -static int -__ec_keyval_set(struct ec_keyval *keyval, struct ec_keyval_elt_ref *ref) -{ - size_t new_size; - uint32_t mask; - int ret; - - /* remove previous entry if any */ - ec_keyval_del(keyval, ref->elt->key); - - if (keyval->len >= keyval->table_size) { - if (keyval->table_size != 0) - new_size = keyval->table_size << FACTOR; - else - new_size = 1 << FACTOR; - ret = ec_keyval_table_resize(keyval, new_size); - if (ret < 0) - return ret; - } - - mask = keyval->table_size - 1; - LIST_INSERT_HEAD(&keyval->table[ref->elt->hash & mask], ref, next); - keyval->len++; - - return 0; -} - -int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val, - ec_keyval_elt_free_t free_cb) -{ - struct ec_keyval_elt *elt = NULL; - struct ec_keyval_elt_ref *ref = NULL; - uint32_t h; - - if (keyval == NULL || key == NULL) { - errno = EINVAL; - return -1; - } - - ref = ec_calloc(1, sizeof(*ref)); - if (ref == NULL) - goto fail; - - elt = ec_calloc(1, sizeof(*elt)); - if (elt == NULL) - goto fail; - - ref->elt = elt; - elt->refcount = 1; - elt->val = val; - val = NULL; - elt->free = free_cb; - elt->key = ec_strdup(key); - if (elt->key == NULL) - goto fail; - h = ec_murmurhash3(key, strlen(key), ec_keyval_seed); - elt->hash = h; - - if (__ec_keyval_set(keyval, ref) < 0) - goto fail; - - return 0; - -fail: - if (free_cb != NULL && val != NULL) - free_cb(val); - ec_keyval_elt_ref_free(ref); - return -1; -} - -void ec_keyval_free(struct ec_keyval *keyval) -{ - struct ec_keyval_elt_ref *ref; - size_t i; - - if (keyval == NULL) - return; - - for (i = 0; i < keyval->table_size; i++) { - while (!LIST_EMPTY(&keyval->table[i])) { - ref = LIST_FIRST(&keyval->table[i]); - LIST_REMOVE(ref, next); - ec_keyval_elt_ref_free(ref); - } - } - ec_free(keyval->table); - ec_free(keyval); -} - -size_t ec_keyval_len(const struct ec_keyval *keyval) -{ - return keyval->len; -} - -void -ec_keyval_iter_free(struct ec_keyval_iter *iter) -{ - ec_free(iter); -} - -struct ec_keyval_iter * -ec_keyval_iter(const struct ec_keyval *keyval) -{ - struct ec_keyval_iter *iter = NULL; - - iter = ec_calloc(1, sizeof(*iter)); - if (iter == NULL) - return NULL; - - iter->keyval = keyval; - iter->cur_idx = 0; - iter->cur_ref = NULL; - - ec_keyval_iter_next(iter); - - return iter; -} - -void -ec_keyval_iter_next(struct ec_keyval_iter *iter) -{ - const struct ec_keyval_elt_ref *ref; - size_t i; - - i = iter->cur_idx; - if (i == iter->keyval->table_size) - return; /* no more element */ - - if (iter->cur_ref != NULL) { - ref = LIST_NEXT(iter->cur_ref, next); - if (ref != NULL) { - iter->cur_ref = ref; - return; - } - i++; - } - - for (; i < iter->keyval->table_size; i++) { - LIST_FOREACH(ref, &iter->keyval->table[i], next) { - iter->cur_idx = i; - iter->cur_ref = LIST_FIRST(&iter->keyval->table[i]); - return; - } - } - - iter->cur_idx = iter->keyval->table_size; - iter->cur_ref = NULL; -} - -bool -ec_keyval_iter_valid(const struct ec_keyval_iter *iter) -{ - if (iter == NULL || iter->cur_ref == NULL) - return false; - - return true; -} - -const char * -ec_keyval_iter_get_key(const struct ec_keyval_iter *iter) -{ - if (iter == NULL || iter->cur_ref == NULL) - return NULL; - - return iter->cur_ref->elt->key; -} - -void * -ec_keyval_iter_get_val(const struct ec_keyval_iter *iter) -{ - if (iter == NULL || iter->cur_ref == NULL) - return NULL; - - return iter->cur_ref->elt->val; -} - -void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval) -{ - struct ec_keyval_iter *iter; - - if (keyval == NULL) { - fprintf(out, "empty keyval\n"); - return; - } - - fprintf(out, "keyval:\n"); - for (iter = ec_keyval_iter(keyval); - ec_keyval_iter_valid(iter); - ec_keyval_iter_next(iter)) { - fprintf(out, " %s: %p\n", - ec_keyval_iter_get_key(iter), - ec_keyval_iter_get_val(iter)); - } - ec_keyval_iter_free(iter); -} - -struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval) -{ - struct ec_keyval *dup = NULL; - struct ec_keyval_elt_ref *ref, *dup_ref = NULL; - size_t i; - - dup = ec_keyval(); - if (dup == NULL) - return NULL; - - for (i = 0; i < keyval->table_size; i++) { - LIST_FOREACH(ref, &keyval->table[i], next) { - dup_ref = ec_calloc(1, sizeof(*ref)); - if (dup_ref == NULL) - goto fail; - dup_ref->elt = ref->elt; - ref->elt->refcount++; - - if (__ec_keyval_set(dup, dup_ref) < 0) - goto fail; - } - } - - return dup; - -fail: - ec_keyval_elt_ref_free(dup_ref); - ec_keyval_free(dup); - return NULL; -} - -static int ec_keyval_init_func(void) -{ - int fd; - ssize_t ret; - - return 0;//XXX for test reproduceability - - fd = open("/dev/urandom", 0); - if (fd == -1) { - fprintf(stderr, "failed to open /dev/urandom\n"); - return -1; - } - ret = read(fd, &ec_keyval_seed, sizeof(ec_keyval_seed)); - if (ret != sizeof(ec_keyval_seed)) { - fprintf(stderr, "failed to read /dev/urandom\n"); - return -1; - } - close(fd); - return 0; -} - -static struct ec_init ec_keyval_init = { - .init = ec_keyval_init_func, - .priority = 50, -}; - -EC_INIT_REGISTER(ec_keyval_init); - -/* LCOV_EXCL_START */ -static int ec_keyval_testcase(void) -{ - struct ec_keyval *keyval, *dup; - struct ec_keyval_iter *iter; - char *val; - size_t i, count; - int ret, testres = 0; - FILE *f = NULL; - char *buf = NULL; - size_t buflen = 0; - - keyval = ec_keyval(); - if (keyval == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create keyval\n"); - return -1; - } - - count = 0; - for (iter = ec_keyval_iter(keyval); - ec_keyval_iter_valid(iter); - ec_keyval_iter_next(iter)) { - count++; - } - ec_keyval_iter_free(iter); - testres |= EC_TEST_CHECK(count == 0, "invalid count in iterator"); - - testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0, "bad keyval len"); - ret = ec_keyval_set(keyval, "key1", "val1", NULL); - testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); - ret = ec_keyval_set(keyval, "key2", ec_strdup("val2"), ec_free_func); - testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); - testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2, "bad keyval len"); - - val = ec_keyval_get(keyval, "key1"); - testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val1"), - "invalid keyval value"); - val = ec_keyval_get(keyval, "key2"); - testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val2"), - "invalid keyval value"); - val = ec_keyval_get(keyval, "key3"); - testres |= EC_TEST_CHECK(val == NULL, "key3 should be NULL"); - - ret = ec_keyval_set(keyval, "key1", "another_val1", NULL); - testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); - ret = ec_keyval_set(keyval, "key2", ec_strdup("another_val2"), - ec_free_func); - testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); - testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2, - "bad keyval len"); - - val = ec_keyval_get(keyval, "key1"); - testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val1"), - "invalid keyval value"); - val = ec_keyval_get(keyval, "key2"); - testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val2"), - "invalid keyval value"); - testres |= EC_TEST_CHECK(ec_keyval_has_key(keyval, "key1"), - "key1 should be in keyval"); - - f = open_memstream(&buf, &buflen); - if (f == NULL) - goto fail; - ec_keyval_dump(f, NULL); - fclose(f); - f = NULL; - free(buf); - buf = NULL; - - f = open_memstream(&buf, &buflen); - if (f == NULL) - goto fail; - ec_keyval_dump(f, keyval); - fclose(f); - f = NULL; - free(buf); - buf = NULL; - - ret = ec_keyval_del(keyval, "key1"); - testres |= EC_TEST_CHECK(ret == 0, "cannot del key1"); - testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 1, - "invalid keyval len"); - ret = ec_keyval_del(keyval, "key2"); - testres |= EC_TEST_CHECK(ret == 0, "cannot del key2"); - testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0, - "invalid keyval len"); - - for (i = 0; i < 100; i++) { - char key[8]; - snprintf(key, sizeof(key), "k%zd", i); - ret = ec_keyval_set(keyval, key, "val", NULL); - testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); - } - dup = ec_keyval_dup(keyval); - testres |= EC_TEST_CHECK(dup != NULL, "cannot duplicate keyval"); - if (dup != NULL) { - for (i = 0; i < 100; i++) { - char key[8]; - snprintf(key, sizeof(key), "k%zd", i); - val = ec_keyval_get(dup, key); - testres |= EC_TEST_CHECK( - val != NULL && !strcmp(val, "val"), - "invalid keyval value"); - } - ec_keyval_free(dup); - dup = NULL; - } - - count = 0; - for (iter = ec_keyval_iter(keyval); - ec_keyval_iter_valid(iter); - ec_keyval_iter_next(iter)) { - count++; - } - ec_keyval_iter_free(iter); - testres |= EC_TEST_CHECK(count == 100, "invalid count in iterator"); - - /* einval */ - ret = ec_keyval_set(keyval, NULL, "val1", NULL); - testres |= EC_TEST_CHECK(ret == -1, "should not be able to set key"); - val = ec_keyval_get(keyval, NULL); - testres |= EC_TEST_CHECK(val == NULL, "get(NULL) should no success"); - - ec_keyval_free(keyval); - - return testres; - -fail: - ec_keyval_free(keyval); - if (f) - fclose(f); - free(buf); - return -1; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_keyval_test = { - .name = "keyval", - .test = ec_keyval_testcase, -}; - -EC_TEST_REGISTER(ec_keyval_test); diff --git a/lib/ecoli_keyval.h b/lib/ecoli_keyval.h deleted file mode 100644 index a3e9400..0000000 --- a/lib/ecoli_keyval.h +++ /dev/null @@ -1,204 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 -#include - -typedef void (*ec_keyval_elt_free_t)(void *); - -struct ec_keyval; -struct ec_keyval_iter; - -/** - * Create a hash table. - * - * @return - * The hash table, or NULL on error (errno is set). - */ -struct ec_keyval *ec_keyval(void); - -/** - * Get a value from the hash table. - * - * @param keyval - * The hash table. - * @param key - * The key string. - * @return - * The element if it is found, or NULL on error (errno is set). - * In case of success but the element is NULL, errno is set to 0. - */ -void *ec_keyval_get(const struct ec_keyval *keyval, const char *key); - -/** - * Check if the hash table contains this key. - * - * @param keyval - * The hash table. - * @param key - * The key string. - * @return - * true if it contains the key, else false. - */ -bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key); - -/** - * Delete an object from the hash table. - * - * @param keyval - * The hash table. - * @param key - * The key string. - * @return - * 0 on success, or -1 on error (errno is set). - */ -int ec_keyval_del(struct ec_keyval *keyval, const char *key); - -/** - * Add/replace an object in the hash table. - * - * @param keyval - * The hash table. - * @param key - * The key string. - * @param val - * The pointer to be saved in the hash table. - * @param free_cb - * An optional pointer to a destructor function called when an - * object is destroyed (ec_keyval_del() or ec_keyval_free()). - * @return - * 0 on success, or -1 on error (errno is set). - * On error, the passed value is freed (free_cb(val) is called). - */ -int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val, - ec_keyval_elt_free_t free_cb); - -/** - * Free a hash table an all its objects. - * - * @param keyval - * The hash table. - */ -void ec_keyval_free(struct ec_keyval *keyval); - -/** - * Get the length of a hash table. - * - * @param keyval - * The hash table. - * @return - * The length of the hash table. - */ -size_t ec_keyval_len(const struct ec_keyval *keyval); - -/** - * Duplicate a hash table - * - * A reference counter is shared between the clones of - * hash tables so that the objects are freed only when - * the last reference is destroyed. - * - * @param keyval - * The hash table. - * @return - * The duplicated hash table, or NULL on error (errno is set). - */ -struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval); - -/** - * Dump a hash table. - * - * @param out - * The stream where the dump is sent. - * @param keyval - * The hash table. - */ -void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval); - -/** - * Iterate the elements in the hash table. - * - * The typical usage is as below: - * - * // dump elements - * for (iter = ec_keyval_iter(keyval); - * ec_keyval_iter_valid(iter); - * ec_keyval_iter_next(iter)) { - * printf(" %s: %p\n", - * ec_keyval_iter_get_key(iter), - * ec_keyval_iter_get_val(iter)); - * } - * ec_keyval_iter_free(iter); - * - * @param keyval - * The hash table. - * @return - * An iterator, or NULL on error (errno is set). - */ -struct ec_keyval_iter * -ec_keyval_iter(const struct ec_keyval *keyval); - -/** - * Make the iterator point to the next element in the hash table. - * - * @param iter - * The hash table iterator. - */ -void ec_keyval_iter_next(struct ec_keyval_iter *iter); - -/** - * Free the iterator. - * - * @param iter - * The hash table iterator. - */ -void ec_keyval_iter_free(struct ec_keyval_iter *iter); - -/** - * Check if the iterator points to a valid element. - * - * @param iter - * The hash table iterator. - * @return - * true if the element is valid, else false. - */ -bool -ec_keyval_iter_valid(const struct ec_keyval_iter *iter); - -/** - * Get the key of the current element. - * - * @param iter - * The hash table iterator. - * @return - * The current element key, or NULL if the iterator points to an - * invalid element. - */ -const char * -ec_keyval_iter_get_key(const struct ec_keyval_iter *iter); - -/** - * Get the value of the current element. - * - * @param iter - * The hash table iterator. - * @return - * The current element value, or NULL if the iterator points to an - * invalid element. - */ -void * -ec_keyval_iter_get_val(const struct ec_keyval_iter *iter); - - -#endif diff --git a/lib/ecoli_log.c b/lib/ecoli_log.c deleted file mode 100644 index e7577cd..0000000 --- a/lib/ecoli_log.c +++ /dev/null @@ -1,228 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#define _GNU_SOURCE /* for vasprintf */ -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(log); - -static ec_log_t ec_log_fct = ec_log_default_cb; -static void *ec_log_opaque; - -struct ec_log_type { - char *name; - enum ec_log_level level; -}; - -static struct ec_log_type *log_types; -static size_t log_types_len; -static enum ec_log_level global_level = EC_LOG_WARNING; - -int ec_log_level_set(enum ec_log_level level) -{ - if (level > EC_LOG_DEBUG) - return -1; - global_level = level; - - return 0; -} - -enum ec_log_level ec_log_level_get(void) -{ - return global_level; -} - -int ec_log_default_cb(int type, enum ec_log_level level, void *opaque, - const char *str) -{ - (void)opaque; - - if (level > ec_log_level_get()) - return 0; - - if (fprintf(stderr, "[%d] %-12s %s", level, ec_log_name(type), str) < 0) - return -1; - - return 0; -} - -int ec_log_fct_register(ec_log_t usr_log, void *opaque) -{ - if (usr_log == NULL) { - ec_log_fct = ec_log_default_cb; - ec_log_opaque = NULL; - } else { - ec_log_fct = usr_log; - ec_log_opaque = opaque; - } - - return 0; -} - -static int -ec_log_lookup(const char *name) -{ - size_t i; - - for (i = 0; i < log_types_len; i++) { - if (log_types[i].name == NULL) - continue; - if (strcmp(name, log_types[i].name) == 0) - return i; - } - - return -1; -} - -int -ec_log_type_register(const char *name) -{ - struct ec_log_type *new_types; - char *copy; - int id; - - id = ec_log_lookup(name); - if (id >= 0) - return id; - - new_types = ec_realloc(log_types, - sizeof(*new_types) * (log_types_len + 1)); - if (new_types == NULL) - return -1; /* errno is set */ - log_types = new_types; - - copy = ec_strdup(name); - if (copy == NULL) - return -1; /* errno is set */ - - id = log_types_len++; - log_types[id].name = copy; - log_types[id].level = EC_LOG_DEBUG; - - return id; -} - -const char * -ec_log_name(int type) -{ - if (type < 0 || (unsigned int)type >= log_types_len) - return "unknown"; - return log_types[type].name; -} - -int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap) -{ - char *s; - int ret; - - /* don't use ec_vasprintf here, because it will call - * ec_malloc(), then ec_log(), ec_vasprintf()... - * -> stack overflow */ - ret = vasprintf(&s, format, ap); - if (ret < 0) - return ret; - - ret = ec_log_fct(type, level, ec_log_opaque, s); - free(s); - - return ret; -} - -int ec_log(int type, enum ec_log_level level, const char *format, ...) -{ - va_list ap; - int ret; - - va_start(ap, format); - ret = ec_vlog(type, level, format, ap); - va_end(ap); - - return ret; -} - -/* LCOV_EXCL_START */ -static int -log_cb(int type, enum ec_log_level level, void *opaque, const char *str) -{ - (void)type; - (void)level; - (void)str; - *(int *)opaque = 1; - - return 0; -} - -static int ec_log_testcase(void) -{ - ec_log_t prev_log_cb; - void *prev_opaque; - const char *logname; - int testres = 0; - int check_cb = 0; - int logtype; - int level; - int ret; - - prev_log_cb = ec_log_fct; - prev_opaque = ec_log_opaque; - - ret = ec_log_fct_register(log_cb, &check_cb); - testres |= EC_TEST_CHECK(ret == 0, - "cannot register log function\n"); - EC_LOG(LOG_ERR, "test\n"); - testres |= EC_TEST_CHECK(check_cb == 1, - "log callback was not invoked\n"); - logtype = ec_log_lookup("dsdedesdes"); - testres |= EC_TEST_CHECK(logtype == -1, - "lookup invalid name should return -1"); - logtype = ec_log_lookup("log"); - logname = ec_log_name(logtype); - testres |= EC_TEST_CHECK(logname != NULL && - !strcmp(logname, "log"), - "cannot get log name\n"); - logname = ec_log_name(-1); - testres |= EC_TEST_CHECK(logname != NULL && - !strcmp(logname, "unknown"), - "cannot get invalid log name\n"); - logname = ec_log_name(34324); - testres |= EC_TEST_CHECK(logname != NULL && - !strcmp(logname, "unknown"), - "cannot get invalid log name\n"); - level = ec_log_level_get(); - ret = ec_log_level_set(2); - testres |= EC_TEST_CHECK(ret == 0 && ec_log_level_get() == 2, - "cannot set log level\n"); - ret = ec_log_level_set(10); - testres |= EC_TEST_CHECK(ret != 0, - "should not be able to set log level\n"); - - ret = ec_log_fct_register(NULL, NULL); - ec_log_level_set(LOG_DEBUG); - EC_LOG(LOG_DEBUG, "test log\n"); - ec_log_level_set(LOG_INFO); - EC_LOG(LOG_DEBUG, "test log (not displayed)\n"); - ec_log_level_set(level); - - ec_log_fct = prev_log_cb; - ec_log_opaque = prev_opaque; - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_log_test = { - .name = "log", - .test = ec_log_testcase, -}; - -EC_TEST_REGISTER(ec_log_test); diff --git a/lib/ecoli_log.h b/lib/ecoli_log.h deleted file mode 100644 index be7e380..0000000 --- a/lib/ecoli_log.h +++ /dev/null @@ -1,238 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 - -#include - -enum ec_log_level { - EC_LOG_EMERG = 0, /* system is unusable */ - EC_LOG_ALERT = 1, /* action must be taken immediately */ - EC_LOG_CRIT = 2, /* critical conditions */ - EC_LOG_ERR = 3, /* error conditions */ - EC_LOG_WARNING = 4, /* warning conditions */ - EC_LOG_NOTICE = 5, /* normal but significant condition */ - EC_LOG_INFO = 6, /* informational */ - EC_LOG_DEBUG = 7, /* debug-level messages */ -}; - -/** - * Register a log type. - * - * This macro defines a function that will be called at startup (using - * the "constructor" attribute). This function register the named type - * passed as argument, and sets a static global variable - * "ec_log_local_type". This variable is used as the default log type - * for this file when using EC_LOG() or EC_VLOG(). - * - * This macro can be present several times in a file. In this case, the - * local log type is set to the last registered type. - * - * On error, the function aborts. - * - * @param name - * The name of the log to be registered. - */ -#define EC_LOG_TYPE_REGISTER(name) \ - static int name##_log_type; \ - static int ec_log_local_type; \ - __attribute__((constructor, used)) \ - static void ec_log_register_##name(void) \ - { \ - ec_log_local_type = ec_log_type_register(#name); \ - ec_assert_print(ec_log_local_type >= 0, \ - "cannot register log type.\n"); \ - name##_log_type = ec_log_local_type; \ - } - -/** - * User log function type. - * - * It is advised that a user-defined log function drops all messages - * that are at least as critical as ec_log_level_get(), as done by the - * default handler. - * - * @param type - * The log type identifier. - * @param level - * The log level. - * @param opaque - * The opaque pointer that was passed to ec_log_fct_register(). - * @param str - * The string to log. - * @return - * 0 on success, -1 on error (errno is set). - */ -typedef int (*ec_log_t)(int type, enum ec_log_level level, void *opaque, - const char *str); - -/** - * Register a user log function. - * - * @param usr_log - * Function pointer that will be invoked for each log call. - * If the parameter is NULL, ec_log_default_cb() is used. - * @param opaque - * Opaque pointer passed to the log function. - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_log_fct_register(ec_log_t usr_log, void *opaque); - -/** - * Register a named log type. - * - * Register a new log type, which is identified by its name. The - * function returns a log identifier associated to the log name. If the - * name is already registered, the function just returns its identifier. - * - * @param name - * The name of the log type. - * @return - * The log type identifier on success (positive or zero), -1 on - * error (errno is set). - */ -int ec_log_type_register(const char *name); - -/** - * Return the log name associated to the log type identifier. - * - * @param type - * The log type identifier. - * @return - * The name associated to the log type, or "unknown". It always return - * a valid string (never NULL). - */ -const char *ec_log_name(int type); - -/** - * Log a formatted string. - * - * @param type - * The log type identifier. - * @param level - * The log level. - * @param format - * The format string, followed by optional arguments. - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_log(int type, enum ec_log_level level, const char *format, ...) - __attribute__((format(__printf__, 3, 4))); - -/** - * Log a formatted string. - * - * @param type - * The log type identifier. - * @param level - * The log level. - * @param format - * The format string. - * @param ap - * The list of arguments. - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap); - -/** - * Log a formatted string using the local log type. - * - * This macro requires that a log type is previously register with - * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type" - * variable. - * - * @param level - * The log level. - * @param format - * The format string, followed by optional arguments. - * @return - * 0 on success, -1 on error (errno is set). - */ -#define EC_LOG(level, args...) ec_log(ec_log_local_type, level, args) - -/** - * Log a formatted string using the local log type. - * - * This macro requires that a log type is previously register with - * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type" - * variable. - * - * @param level - * The log level. - * @param format - * The format string. - * @param ap - * The list of arguments. - * @return - * 0 on success, -1 on error (errno is set). - */ -#define EC_VLOG(level, fmt, ap) ec_vlog(ec_log_local_type, level, fmt, ap) - -/** - * Default log handler. - * - * This is the default log function that is used by the library. By - * default, it prints all logs whose level is WARNING or more critical. - * This level can be changed with ec_log_level_set(). - * - * @param type - * The log type identifier. - * @param level - * The log level. - * @param opaque - * Unused. - * @param str - * The string to be logged. - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_log_default_cb(int type, enum ec_log_level level, void *opaque, - const char *str); - -/** - * Set the global log level. - * - * This level is used by the default log handler, ec_log_default_cb(). - * All messages that are at least as critical as the default level are - * displayed. - * - * It is advised - * - * @param level - * The log level to be set. - * @return - * 0 on success, -1 on error. - */ -int ec_log_level_set(enum ec_log_level level); - -/** - * Get the global log level. - * - * This level is used by the default log handler, ec_log_default_cb(). - * All messages that are at least as critical as the default level are - * displayed. - * - * @param level - * The log level to be set. - * @return - * 0 on success, -1 on error. - */ -enum ec_log_level ec_log_level_get(void); - -#endif diff --git a/lib/ecoli_malloc.c b/lib/ecoli_malloc.c deleted file mode 100644 index 505f49f..0000000 --- a/lib/ecoli_malloc.c +++ /dev/null @@ -1,174 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include - -#include -#include -#include - -EC_LOG_TYPE_REGISTER(malloc); - -static int init_done = 0; - -struct ec_malloc_handler ec_malloc_handler; - -int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free, - ec_realloc_t usr_realloc) -{ - if (usr_malloc == NULL || usr_free == NULL || usr_realloc == NULL) { - errno = EINVAL; - return -1; - } - - if (init_done) { - errno = EBUSY; - return -1; - } - - ec_malloc_handler.malloc = usr_malloc; - ec_malloc_handler.free = usr_free; - ec_malloc_handler.realloc = usr_realloc; - - return 0; -} - -void *__ec_malloc(size_t size, const char *file, unsigned int line) -{ - return ec_malloc_handler.malloc(size, file, line); -} - -void *ec_malloc_func(size_t size) -{ - return __ec_malloc(size, __FILE__, __LINE__); -} - -void __ec_free(void *ptr, const char *file, unsigned int line) -{ - ec_malloc_handler.free(ptr, file, line); -} - -void ec_free_func(void *ptr) -{ - __ec_free(ptr, __FILE__, __LINE__); -} - -void *__ec_calloc(size_t nmemb, size_t size, const char *file, - unsigned int line) -{ - void *ptr; - size_t total; - - /* check overflow */ - total = size * nmemb; - if (nmemb != 0 && size != (total / nmemb)) { - errno = ENOMEM; - return NULL; - } - - ptr = __ec_malloc(total, file, line); - if (ptr == NULL) - return NULL; - - memset(ptr, 0, total); - return ptr; -} - -void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line) -{ - return ec_malloc_handler.realloc(ptr, size, file, line); -} - -char *__ec_strdup(const char *s, const char *file, unsigned int line) -{ - size_t sz = strlen(s) + 1; - char *s2; - - s2 = __ec_malloc(sz, file, line); - if (s2 == NULL) - return NULL; - - memcpy(s2, s, sz); - - return s2; -} - -char *__ec_strndup(const char *s, size_t n, const char *file, unsigned int line) -{ - size_t sz = strnlen(s, n); - char *s2; - - s2 = __ec_malloc(sz + 1, file, line); - if (s2 == NULL) - return NULL; - - memcpy(s2, s, sz); - s2[sz] = '\0'; - - return s2; -} - -static int ec_malloc_init_func(void) -{ - init_done = 1; - return 0; -} - -static struct ec_init ec_malloc_init = { - .init = ec_malloc_init_func, - .priority = 40, -}; - -EC_INIT_REGISTER(ec_malloc_init); - -/* LCOV_EXCL_START */ -static int ec_malloc_testcase(void) -{ - int ret, testres = 0; - char *ptr, *ptr2; - - ret = ec_malloc_register(NULL, NULL, NULL); - testres |= EC_TEST_CHECK(ret == -1, - "should not be able to register NULL malloc handlers"); - ret = ec_malloc_register(__ec_malloc, __ec_free, __ec_realloc); - testres |= EC_TEST_CHECK(ret == -1, - "should not be able to register after init"); - - /* registration is tested in the test main.c */ - - ptr = ec_malloc(10); - if (ptr == NULL) - return -1; - memset(ptr, 0, 10); - ptr2 = ec_realloc(ptr, 20); - EC_TEST_CHECK(ptr2 != NULL, "cannot realloc ptr\n"); - if (ptr2 == NULL) - ec_free(ptr); - else - ec_free(ptr2); - ptr = NULL; - ptr2 = NULL; - - ptr = ec_malloc_func(10); - if (ptr == NULL) - return -1; - memset(ptr, 0, 10); - ec_free_func(ptr); - ptr = NULL; - - ptr = ec_calloc(2, (size_t)-1); - EC_TEST_CHECK(ptr == NULL, "bad overflow check in ec_calloc\n"); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_malloc_test = { - .name = "malloc", - .test = ec_malloc_testcase, -}; - -EC_TEST_REGISTER(ec_malloc_test); diff --git a/lib/ecoli_malloc.h b/lib/ecoli_malloc.h deleted file mode 100644 index e80c3d9..0000000 --- a/lib/ecoli_malloc.h +++ /dev/null @@ -1,247 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 -#include -#include - -/** - * Function type of malloc, passed to ec_malloc_register(). - * - * The API is the same than malloc(), excepted the file and line - * arguments. - * - * @param size - * The size of the memory area to allocate. - * @param file - * The path to the file that invoked the malloc. - * @param line - * The line in the file that invoked the malloc. - * @return - * A pointer to the allocated memory area, or NULL on error (errno - * is set). - */ -typedef void *(*ec_malloc_t)(size_t size, const char *file, unsigned int line); - -/** - * Function type of free, passed to ec_malloc_register(). - * - * The API is the same than free(), excepted the file and line - * arguments. - * - * @param ptr - * The pointer to the memory area to be freed. - * @param file - * The path to the file that invoked the malloc. - * @param line - * The line in the file that invoked the malloc. - */ -typedef void (*ec_free_t)(void *ptr, const char *file, unsigned int line); - -/** - * Function type of realloc, passed to ec_malloc_register(). - * - * The API is the same than realloc(), excepted the file and line - * arguments. - * - * @param ptr - * The pointer to the memory area to be reallocated. - * @param file - * The path to the file that invoked the malloc. - * @param line - * The line in the file that invoked the malloc. - * @return - * A pointer to the allocated memory area, or NULL on error (errno - * is set). - */ -typedef void *(*ec_realloc_t)(void *ptr, size_t size, const char *file, - unsigned int line); - -/** - * Register allocation functions. - * - * This function can be use to register another allocator - * to be used by libecoli. By default, ec_malloc(), ec_free() and - * ec_realloc() use the standard libc allocator. Another handler - * can be used for debug purposes or when running in a specific - * environment. - * - * This function must be called before ec_init(). - * - * @param usr_malloc - * A user-defined malloc function. - * @param usr_free - * A user-defined free function. - * @param usr_realloc - * A user-defined realloc function. - * @return - * 0 on success, or -1 on error (errno is set). - */ -int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free, - ec_realloc_t usr_realloc); - -struct ec_malloc_handler { - ec_malloc_t malloc; - ec_free_t free; - ec_realloc_t realloc; -}; - -extern struct ec_malloc_handler ec_malloc_handler; - -/** - * Allocate a memory area. - * - * Like malloc(), ec_malloc() allocates size bytes and returns a pointer - * to the allocated memory. The memory is not initialized. The memory is - * freed with ec_free(). - * - * @param size - * The size of the area to allocate in bytes. - * @return - * The pointer to the allocated memory, or NULL on error (errno is set). - */ -#define ec_malloc(size) ({ \ - void *ret_; \ - if (ec_malloc_handler.malloc == NULL) \ - ret_ = malloc(size); \ - else \ - ret_ = __ec_malloc(size, __FILE__, __LINE__); \ - ret_; \ - }) - -/** - * Ecoli malloc function. - * - * Use this function when the macro ec_malloc() cannot be used, - * for instance when it is passed as a callback pointer. - */ -void *ec_malloc_func(size_t size); - -/** - * Free a memory area. - * - * Like free(), ec_free() frees the area pointed by ptr, which must have - * been returned by a previous call to ec_malloc() or any other - * allocation function of this file. - * - * @param ptr - * The pointer to the memory area. - */ -#define ec_free(ptr) ({ \ - if (ec_malloc_handler.free == NULL) \ - free(ptr); \ - else \ - __ec_free(ptr, __FILE__, __LINE__); \ - }) - -/** - * Ecoli free function. - * - * Use this function when the macro ec_free() cannot be used, - * for instance when it is passed as a callback pointer. - */ -void ec_free_func(void *ptr); - -/** - * Resize an allocated memory area. - * - * @param ptr - * The pointer to the previously allocated memory area, or NULL. - * @param size - * The new size of the memory area. - * @return - * A pointer to the newly allocated memory, or NULL if the request - * fails. In that case, the original area is left untouched. - */ -#define ec_realloc(ptr, size) ({ \ - void *ret_; \ - if (ec_malloc_handler.realloc == NULL) \ - ret_ = realloc(ptr, size); \ - else \ - ret_ = __ec_realloc(ptr, size, __FILE__, __LINE__); \ - ret_; \ - }) - -/** - * Allocate and initialize an array of elements. - * - * @param n - * The number of elements. - * @param size - * The size of each element. - * @return - * The pointer to the allocated memory, or NULL on error (errno is set). - */ -#define ec_calloc(n, size) ({ \ - void *ret_; \ - if (ec_malloc_handler.malloc == NULL) \ - ret_ = calloc(n, size); \ - else \ - ret_ = __ec_calloc(n, size, __FILE__, __LINE__); \ - ret_; \ - }) - -/** - * Duplicate a string. - * - * Memory for the new string is obtained with ec_malloc(), and can be - * freed with ec_free(). - * - * @param s - * The string to be duplicated. - * @return - * The pointer to the duplicated string, or NULL on error (errno is set). - */ -#define ec_strdup(s) ({ \ - void *ret_; \ - if (ec_malloc_handler.malloc == NULL) \ - ret_ = strdup(s); \ - else \ - ret_ = __ec_strdup(s, __FILE__, __LINE__); \ - ret_; \ - }) - -/** - * Duplicate at most n bytes of a string. - * - * This function is similar to ec_strdup(), except that it copies at - * most n bytes. If s is longer than n, only n bytes are copied, and a - * terminating null byte ('\0') is added. - * - * @param s - * The string to be duplicated. - * @param n - * The maximum length of the new string. - * @return - * The pointer to the duplicated string, or NULL on error (errno is set). - */ -#define ec_strndup(s, n) ({ \ - void *ret_; \ - if (ec_malloc_handler.malloc == NULL) \ - ret_ = strndup(s, n); \ - else \ - ret_ = __ec_strndup(s, n, __FILE__, __LINE__); \ - ret_; \ - }) - -/* internal */ -void *__ec_malloc(size_t size, const char *file, unsigned int line); -void __ec_free(void *ptr, const char *file, unsigned int line); -void *__ec_calloc(size_t nmemb, size_t size, const char *file, - unsigned int line); -void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line); -char *__ec_strdup(const char *s, const char *file, unsigned int line); -char *__ec_strndup(const char *s, size_t n, const char *file, - unsigned int line); - - -#endif diff --git a/lib/ecoli_murmurhash.c b/lib/ecoli_murmurhash.c deleted file mode 100644 index 7aafece..0000000 --- a/lib/ecoli_murmurhash.c +++ /dev/null @@ -1,40 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include - -#include - -uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed) -{ - const uint8_t *data = (const uint8_t *)key; - const uint8_t *tail; - const int nblocks = len / 4; - uint32_t h1 = seed; - uint32_t k1; - const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); - int i; - - for (i = -nblocks; i; i++) { - k1 = blocks[i]; - - h1 = ec_murmurhash3_add32(h1, k1); - h1 = ec_murmurhash3_mix32(h1); - } - - tail = (const uint8_t *)(data + nblocks * 4); - k1 = 0; - - switch(len & 3) { - case 3: k1 ^= tail[2] << 16; - case 2: k1 ^= tail[1] << 8; - case 1: k1 ^= tail[0]; - h1 = ec_murmurhash3_add32(h1, k1); - }; - - /* finalization */ - h1 ^= len; - h1 = ec_murmurhash3_fmix32(h1); - return h1; -} diff --git a/lib/ecoli_murmurhash.h b/lib/ecoli_murmurhash.h deleted file mode 100644 index 6b76d34..0000000 --- a/lib/ecoli_murmurhash.h +++ /dev/null @@ -1,66 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 - -/** Hash rotation */ -static inline uint32_t ec_murmurhash_rotl32(uint32_t x, int8_t r) -{ - return (x << r) | (x >> (32 - r)); -} - -/** Add 32-bit to the hash */ -static inline uint32_t ec_murmurhash3_add32(uint32_t h, uint32_t data) -{ - data *= 0xcc9e2d51; - data = ec_murmurhash_rotl32(data, 15); - data *= 0x1b873593; - h ^= data; - return h; -} - -/** Intermediate mix */ -static inline uint32_t ec_murmurhash3_mix32(uint32_t h) -{ - h = ec_murmurhash_rotl32(h,13); - h = h * 5 +0xe6546b64; - return h; -} - -/** Final mix: force all bits of a hash block to avalanche */ -static inline uint32_t ec_murmurhash3_fmix32(uint32_t h) -{ - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - - return h; -} - -/** - * Calculate a 32-bit murmurhash3 - * - * @param key - * The key (the unaligned variable-length array of bytes). - * @param len - * The length of the key, counting by bytes. - * @param seed - * Can be any 4-byte value initialization value. - * @return - * A 32-bit hash. - */ -uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed); - -#endif /* ECOLI_MURMURHASH_H_ */ diff --git a/lib/ecoli_node.c b/lib/ecoli_node.c deleted file mode 100644 index c02a8de..0000000 --- a/lib/ecoli_node.c +++ /dev/null @@ -1,607 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -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), ""), - "bad child 0"); - - testres |= EC_TEST_CHECK( - ec_node_get_children_count(node) == 2, - "bad children count\n"); - ret = ec_node_get_child(node, 0, &child, &refs); - testres |= EC_TEST_CHECK(ret == 0 && - child != NULL && - !strcmp(ec_node_type(child)->name, "str") && - !strcmp(ec_node_id(child), "id_x"), - "bad child 0"); - ret = ec_node_get_child(node, 1, &child, &refs); - testres |= EC_TEST_CHECK(ret == 0 && - child != NULL && - !strcmp(ec_node_type(child)->name, "str") && - !strcmp(ec_node_id(child), "id_y"), - "bad child 1"); - ret = ec_node_get_child(node, 2, &child, &refs); - testres |= EC_TEST_CHECK(ret != 0, - "ret should be != 0"); - testres |= EC_TEST_CHECK(child == NULL, - "child 2 should be NULL"); - - child = ec_node_find(node, "id_x"); - testres |= EC_TEST_CHECK(child != NULL && - !strcmp(ec_node_type(child)->name, "str") && - !strcmp(ec_node_id(child), "id_x") && - !strcmp(ec_node_desc(child), "x"), - "bad child id_x"); - child = ec_node_find(node, "id_dezdex"); - testres |= EC_TEST_CHECK(child == NULL, - "child with wrong id should be NULL"); - - ret = ec_keyval_set(ec_node_attrs(node), "key", "val", NULL); - testres |= EC_TEST_CHECK(ret == 0, - "cannot set node attribute\n"); - - type = ec_node_type_lookup("seq"); - testres |= EC_TEST_CHECK(type != NULL && - ec_node_check_type(node, type) == 0, - "cannot get seq node type"); - type = ec_node_type_lookup("str"); - testres |= EC_TEST_CHECK(type != NULL && - ec_node_check_type(node, type) < 0, - "node type should not be str"); - - ec_node_free(node); - node = NULL; - - node = ec_node("deznuindez", EC_NO_ID); - testres |= EC_TEST_CHECK(node == NULL, - "should not be able to create node\n"); - - /* test loop */ - expr = ec_node("or", EC_NO_ID); - val = ec_node_int(EC_NO_ID, 0, 10, 0); - op = ec_node_str(EC_NO_ID, "!"); - seq = EC_NODE_SEQ(EC_NO_ID, - op, - ec_node_clone(expr)); - op = NULL; - if (expr == NULL || val == NULL || seq == NULL) - goto fail; - if (ec_node_or_add(expr, ec_node_clone(seq)) < 0) - goto fail; - ec_node_free(seq); - seq = NULL; - if (ec_node_or_add(expr, ec_node_clone(val)) < 0) - goto fail; - ec_node_free(val); - val = NULL; - - testres |= EC_TEST_CHECK_PARSE(expr, 1, "1"); - testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1"); - testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!"); - - ec_node_free(expr); - expr = NULL; - - /* same loop test, but keep some refs (released later) */ - expr = ec_node("or", EC_NO_ID); - ec_node_clone(expr); - expr2 = expr; - val = ec_node_int(EC_NO_ID, 0, 10, 0); - op = ec_node_str(EC_NO_ID, "!"); - seq = EC_NODE_SEQ(EC_NO_ID, - op, - ec_node_clone(expr)); - op = NULL; - if (expr == NULL || val == NULL || seq == NULL) - goto fail; - if (ec_node_or_add(expr, ec_node_clone(seq)) < 0) - goto fail; - ec_node_free(seq); - seq = NULL; - if (ec_node_or_add(expr, ec_node_clone(val)) < 0) - goto fail; - - testres |= EC_TEST_CHECK_PARSE(expr, 1, "1"); - testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1"); - testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!"); - - ec_node_free(expr2); - expr2 = NULL; - ec_node_free(val); - val = NULL; - ec_node_free(expr); - expr = NULL; - - return testres; - -fail: - ec_node_free(expr); - ec_node_free(expr2); - ec_node_free(val); - ec_node_free(seq); - ec_node_free(node); - if (f != NULL) - fclose(f); - free(buf); - - assert(errno != 0); - return -1; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_test = { - .name = "node", - .test = ec_node_testcase, -}; - -EC_TEST_REGISTER(ec_node_test); diff --git a/lib/ecoli_node.h b/lib/ecoli_node.h deleted file mode 100644 index 45f3e74..0000000 --- a/lib/ecoli_node.h +++ /dev/null @@ -1,206 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 -#include -#include - -#define EC_NO_ID "no-id" - -#define EC_NODE_ENDLIST ((void *)1) - -struct ec_node; -struct ec_parse; -struct ec_comp; -struct ec_strvec; -struct ec_keyval; -struct ec_config; -struct ec_config_schema; - -#define EC_NODE_TYPE_REGISTER(t) \ - static void ec_node_init_##t(void); \ - static void __attribute__((constructor, used)) \ - ec_node_init_##t(void) \ - { \ - if (ec_node_type_register(&t) < 0) \ - fprintf(stderr, \ - "cannot register node type %s\n", \ - t.name); \ - } - -TAILQ_HEAD(ec_node_type_list, ec_node_type); - -typedef int (*ec_node_set_config_t)(struct ec_node *node, - const struct ec_config *config); -typedef int (*ec_node_parse_t)(const struct ec_node *node, - struct ec_parse *state, - const struct ec_strvec *strvec); -typedef int (*ec_node_complete_t)(const struct ec_node *node, - struct ec_comp *comp_state, - const struct ec_strvec *strvec); -typedef const char * (*ec_node_desc_t)(const struct ec_node *); -typedef int (*ec_node_init_priv_t)(struct ec_node *); -typedef void (*ec_node_free_priv_t)(struct ec_node *); -typedef size_t (*ec_node_get_children_count_t)(const struct ec_node *); -typedef int (*ec_node_get_child_t)(const struct ec_node *, - size_t i, struct ec_node **child, unsigned int *refs); - -/** - * A structure describing a node type. - */ -struct ec_node_type { - TAILQ_ENTRY(ec_node_type) next; /**< Next in list. */ - const char *name; /**< Node type name. */ - /** Configuration schema array, must be terminated by a sentinel - * (.type = EC_CONFIG_TYPE_NONE). */ - const struct ec_config_schema *schema; - ec_node_set_config_t set_config; /* validate/ack a config change */ - ec_node_parse_t parse; - ec_node_complete_t complete; - ec_node_desc_t desc; - size_t size; - ec_node_init_priv_t init_priv; - ec_node_free_priv_t free_priv; - ec_node_get_children_count_t get_children_count; - ec_node_get_child_t get_child; -}; - -/** - * Register a node type. - * - * @param type - * A pointer to a ec_test structure describing the test - * to be registered. - * @return - * 0 on success, negative value on error. - */ -int ec_node_type_register(struct ec_node_type *type); - -/** - * Lookup node type by name - * - * @param name - * The name of the node type to search. - * @return - * The node type if found, or NULL on error. - */ -const struct ec_node_type *ec_node_type_lookup(const char *name); - -/** - * Dump registered log types - */ -void ec_node_type_dump(FILE *out); - -/** - * Get the config schema of a node type. - */ -const struct ec_config_schema * -ec_node_type_schema(const struct ec_node_type *type); - -/** - * Get the name of a node type. - */ -const char * -ec_node_type_name(const struct ec_node_type *type); - -enum ec_node_free_state { - EC_NODE_FREE_STATE_NONE, - EC_NODE_FREE_STATE_TRAVERSED, - EC_NODE_FREE_STATE_FREEABLE, - EC_NODE_FREE_STATE_NOT_FREEABLE, - EC_NODE_FREE_STATE_FREEING, -}; - -struct ec_node { - const struct ec_node_type *type; - struct ec_config *config; /**< Generic configuration. */ - char *id; - char *desc; - struct ec_keyval *attrs; - unsigned int refcnt; - struct { - enum ec_node_free_state state; /**< State of loop detection */ - unsigned int refcnt; /**< Number of reachable references - * starting from node beeing freed */ - } free; /**< Freeing state: used for loop detection */ -}; - -/* create a new node when the type is known, typically called from the node - * code */ -struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id); - -/* create a new node */ -struct ec_node *ec_node(const char *typename, const char *id); - -struct ec_node *ec_node_clone(struct ec_node *node); -void ec_node_free(struct ec_node *node); - -/* set configuration of a node - * after a call to this function, the config is - * owned by the node and must not be used by the caller - * on error, the config is freed. */ -int ec_node_set_config(struct ec_node *node, struct ec_config *config); - -/* get the current node configuration. Return NULL if no configuration. */ -const struct ec_config *ec_node_get_config(struct ec_node *node); - -size_t ec_node_get_children_count(const struct ec_node *node); -int -ec_node_get_child(const struct ec_node *node, size_t i, - struct ec_node **child, unsigned int *refs); - -/* XXX add more accessors */ -const struct ec_node_type *ec_node_type(const struct ec_node *node); -struct ec_keyval *ec_node_attrs(const struct ec_node *node); -const char *ec_node_id(const struct ec_node *node); -const char *ec_node_desc(const struct ec_node *node); - -void ec_node_dump(FILE *out, const struct ec_node *node); -struct ec_node *ec_node_find(struct ec_node *node, const char *id); - -/* check the type of a node */ -int ec_node_check_type(const struct ec_node *node, - const struct ec_node_type *type); - -#endif diff --git a/lib/ecoli_node_any.c b/lib/ecoli_node_any.c deleted file mode 100644 index 197a2c5..0000000 --- a/lib/ecoli_node_any.c +++ /dev/null @@ -1,86 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_any); - -struct ec_node_any { - struct ec_node gen; -}; - -static int ec_node_any_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - (void)gen_node; - (void)state; - - if (ec_strvec_len(strvec) == 0) - return EC_PARSE_NOMATCH; - - return 1; -} - -static struct ec_node_type ec_node_any_type = { - .name = "any", - .parse = ec_node_any_parse, - .complete = ec_node_complete_unknown, - .size = sizeof(struct ec_node_any), -}; - -EC_NODE_TYPE_REGISTER(ec_node_any_type); - -/* LCOV_EXCL_START */ -static int ec_node_any_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node("any", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1); - ec_node_free(node); - - /* never completes */ - node = ec_node("any", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_any_test = { - .name = "node_any", - .test = ec_node_any_testcase, -}; - -EC_TEST_REGISTER(ec_node_any_test); diff --git a/lib/ecoli_node_any.h b/lib/ecoli_node_any.h deleted file mode 100644 index ee638aa..0000000 --- a/lib/ecoli_node_any.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * This node always matches 1 string in the vector - */ - -#ifndef ECOLI_NODE_ANY_ -#define ECOLI_NODE_ANY_ - -/* no specific API for this node */ - -#endif diff --git a/lib/ecoli_node_cmd.c b/lib/ecoli_node_cmd.c deleted file mode 100644 index c61b759..0000000 --- a/lib/ecoli_node_cmd.c +++ /dev/null @@ -1,682 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_cmd); - -struct ec_node_cmd { - struct ec_node gen; - char *cmd_str; /* the command string. */ - struct ec_node *cmd; /* the command node. */ - struct ec_node *parser; /* the expression parser. */ - struct ec_node *expr; /* the expression parser without lexer. */ - struct ec_node **table; /* table of node referenced in command. */ - unsigned int len; /* len of the table. */ -}; - -/* passed as user context to expression parser */ -struct ec_node_cmd_ctx { - struct ec_node **table; - unsigned int len; -}; - -static int -ec_node_cmd_eval_var(void **result, void *userctx, - const struct ec_parse *var) -{ - const struct ec_strvec *vec; - struct ec_node_cmd_ctx *ctx = userctx; - struct ec_node *eval = NULL; - const char *str, *id; - unsigned int i; - - /* get parsed string vector, it should contain only one str */ - vec = ec_parse_strvec(var); - if (ec_strvec_len(vec) != 1) { - errno = EINVAL; - return -1; - } - str = ec_strvec_val(vec, 0); - - for (i = 0; i < ctx->len; i++) { - id = ec_node_id(ctx->table[i]); - if (id == NULL) - continue; - if (strcmp(str, id)) - continue; - /* if id matches, use a node provided by the user... */ - eval = ec_node_clone(ctx->table[i]); - if (eval == NULL) - return -1; - break; - } - - /* ...or create a string node */ - if (eval == NULL) { - eval = ec_node_str(EC_NO_ID, str); - if (eval == NULL) - return -1; - } - - *result = eval; - - return 0; -} - -static int -ec_node_cmd_eval_pre_op(void **result, void *userctx, void *operand, - const struct ec_parse *operator) -{ - (void)result; - (void)userctx; - (void)operand; - (void)operator; - - errno = EINVAL; - return -1; -} - -static int -ec_node_cmd_eval_post_op(void **result, void *userctx, void *operand, - const struct ec_parse *operator) -{ - const struct ec_strvec *vec; - struct ec_node *in = operand;; - struct ec_node *out = NULL;; - - (void)userctx; - - /* get parsed string vector, it should contain only one str */ - vec = ec_parse_strvec(operator); - if (ec_strvec_len(vec) != 1) { - errno = EINVAL; - return -1; - } - - if (!strcmp(ec_strvec_val(vec, 0), "*")) { - out = ec_node_many(EC_NO_ID, - ec_node_clone(in), 0, 0); - if (out == NULL) - return -1; - ec_node_free(in); - *result = out; - } else { - errno = EINVAL; - return -1; - } - - return 0; -} - -static int -ec_node_cmd_eval_bin_op(void **result, void *userctx, void *operand1, - const struct ec_parse *operator, void *operand2) - -{ - const struct ec_strvec *vec; - struct ec_node *out = NULL; - struct ec_node *in1 = operand1; - struct ec_node *in2 = operand2; - - (void)userctx; - - /* get parsed string vector, it should contain only one str */ - vec = ec_parse_strvec(operator); - if (ec_strvec_len(vec) > 1) { - errno = EINVAL; - return -1; - } - - if (ec_strvec_len(vec) == 0) { - if (!strcmp(in1->type->name, "seq")) { - if (ec_node_seq_add(in1, ec_node_clone(in2)) < 0) - return -1; - ec_node_free(in2); - *result = in1; - } else { - out = EC_NODE_SEQ(EC_NO_ID, ec_node_clone(in1), - ec_node_clone(in2)); - if (out == NULL) - return -1; - ec_node_free(in1); - ec_node_free(in2); - *result = out; - } - } else if (!strcmp(ec_strvec_val(vec, 0), "|")) { - if (!strcmp(in2->type->name, "or")) { - if (ec_node_or_add(in2, ec_node_clone(in1)) < 0) - return -1; - ec_node_free(in1); - *result = in2; - } else if (!strcmp(in1->type->name, "or")) { - if (ec_node_or_add(in1, ec_node_clone(in2)) < 0) - return -1; - ec_node_free(in2); - *result = in1; - } else { - out = EC_NODE_OR(EC_NO_ID, ec_node_clone(in1), - ec_node_clone(in2)); - if (out == NULL) - return -1; - ec_node_free(in1); - ec_node_free(in2); - *result = out; - } - } else if (!strcmp(ec_strvec_val(vec, 0), ",")) { - if (!strcmp(in2->type->name, "subset")) { - if (ec_node_subset_add(in2, ec_node_clone(in1)) < 0) - return -1; - ec_node_free(in1); - *result = in2; - } else if (!strcmp(in1->type->name, "subset")) { - if (ec_node_subset_add(in1, ec_node_clone(in2)) < 0) - return -1; - ec_node_free(in2); - *result = in1; - } else { - out = EC_NODE_SUBSET(EC_NO_ID, ec_node_clone(in1), - ec_node_clone(in2)); - if (out == NULL) - return -1; - ec_node_free(in1); - ec_node_free(in2); - *result = out; - } - } else { - errno = EINVAL; - return -1; - } - - return 0; -} - -static int -ec_node_cmd_eval_parenthesis(void **result, void *userctx, - const struct ec_parse *open_paren, - const struct ec_parse *close_paren, - void *value) -{ - const struct ec_strvec *vec; - struct ec_node *in = value;; - struct ec_node *out = NULL;; - - (void)userctx; - (void)close_paren; - - /* get parsed string vector, it should contain only one str */ - vec = ec_parse_strvec(open_paren); - if (ec_strvec_len(vec) != 1) { - errno = EINVAL; - return -1; - } - - if (!strcmp(ec_strvec_val(vec, 0), "[")) { - out = ec_node_option(EC_NO_ID, ec_node_clone(in)); - if (out == NULL) - return -1; - ec_node_free(in); - } else if (!strcmp(ec_strvec_val(vec, 0), "(")) { - out = in; - } else { - errno = EINVAL; - return -1; - } - - *result = out; - - return 0; -} - -static void -ec_node_cmd_eval_free(void *result, void *userctx) -{ - (void)userctx; - ec_free(result); -} - -static const struct ec_node_expr_eval_ops expr_ops = { - .eval_var = ec_node_cmd_eval_var, - .eval_pre_op = ec_node_cmd_eval_pre_op, - .eval_post_op = ec_node_cmd_eval_post_op, - .eval_bin_op = ec_node_cmd_eval_bin_op, - .eval_parenthesis = ec_node_cmd_eval_parenthesis, - .eval_free = ec_node_cmd_eval_free, -}; - -static struct ec_node * -ec_node_cmd_build_expr(void) -{ - struct ec_node *expr = NULL; - int ret; - - /* build the expression parser */ - expr = ec_node("expr", "expr"); - if (expr == NULL) - goto fail; - ret = ec_node_expr_set_val_node(expr, ec_node_re(EC_NO_ID, - "[a-zA-Z0-9]+")); - if (ret < 0) - goto fail; - ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, ",")); - if (ret < 0) - goto fail; - ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, "|")); - if (ret < 0) - goto fail; - ret = ec_node_expr_add_bin_op(expr, ec_node("empty", EC_NO_ID)); - if (ret < 0) - goto fail; - ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "+")); - if (ret < 0) - goto fail; - ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "*")); - if (ret < 0) - goto fail; - ret = ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "["), - ec_node_str(EC_NO_ID, "]")); - if (ret < 0) - goto fail; - ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "("), - ec_node_str(EC_NO_ID, ")")); - if (ret < 0) - goto fail; - - return expr; - -fail: - ec_node_free(expr); - return NULL; -} - -static struct ec_node * -ec_node_cmd_build_parser(struct ec_node *expr) -{ - struct ec_node *lex = NULL; - int ret; - - /* prepend a lexer to the expression node */ - lex = ec_node_re_lex(EC_NO_ID, ec_node_clone(expr)); - if (lex == NULL) - goto fail; - - ret = ec_node_re_lex_add(lex, "[a-zA-Z0-9]+", 1); - if (ret < 0) - goto fail; - ret = ec_node_re_lex_add(lex, "[*|,()]", 1); - if (ret < 0) - goto fail; - ret = ec_node_re_lex_add(lex, "\\[", 1); - if (ret < 0) - goto fail; - ret = ec_node_re_lex_add(lex, "\\]", 1); - if (ret < 0) - goto fail; - ret = ec_node_re_lex_add(lex, "[ ]+", 0); - if (ret < 0) - goto fail; - - return lex; - -fail: - ec_node_free(lex); - - return NULL; -} - -static struct ec_node * -ec_node_cmd_build(struct ec_node_cmd *node, const char *cmd_str, - struct ec_node **table, size_t len) -{ - struct ec_node_cmd_ctx ctx = { table, len }; - struct ec_parse *p = NULL; - void *result; - int ret; - - /* parse the command expression */ - p = ec_node_parse(node->parser, cmd_str); - if (p == NULL) - goto fail; - - if (!ec_parse_matches(p)) { - errno = EINVAL; - goto fail; - } - if (!ec_parse_has_child(p)) { - errno = EINVAL; - goto fail; - } - - ret = ec_node_expr_eval(&result, node->expr, - ec_parse_get_first_child(p), - &expr_ops, &ctx); - if (ret < 0) - goto fail; - - ec_parse_free(p); - return result; - -fail: - ec_parse_free(p); - return NULL; -} - -static int -ec_node_cmd_parse(const struct ec_node *gen_node, struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; - - return ec_node_parse_child(node->cmd, state, strvec); -} - -static int -ec_node_cmd_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; - - return ec_node_complete_child(node->cmd, comp, strvec); -} - -static void ec_node_cmd_free_priv(struct ec_node *gen_node) -{ - struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; - size_t i; - - ec_free(node->cmd_str); - node->cmd_str = NULL; - ec_node_free(node->expr); - node->expr = NULL; - ec_node_free(node->parser); - node->parser = NULL; - ec_node_free(node->cmd); - node->cmd = NULL; - for (i = 0; i < node->len; i++) - ec_node_free(node->table[i]); - ec_free(node->table); - node->table = NULL; - node->len = 0; -} - -static const struct ec_config_schema ec_node_cmd_subschema[] = { - { - .desc = "A child node whose id is referenced in the expression.", - .type = EC_CONFIG_TYPE_NODE, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static const struct ec_config_schema ec_node_cmd_schema[] = { - { - .key = "expr", - .desc = "The expression to match. Supported operators " - "are or '|', list ',', many '+', many-or-zero '*', " - "option '[]', group '()'. An identifier (alphanumeric) can " - "reference a node whose node_id matches. Else it is " - "interpreted as ec_node_str() matching this string. " - "Example: command [option] (subset1, subset2) x|y", - .type = EC_CONFIG_TYPE_STRING, - }, - { - .key = "children", - .desc = "The list of children nodes.", - .type = EC_CONFIG_TYPE_LIST, - .subschema = ec_node_cmd_subschema, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_cmd_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; - const struct ec_config *expr = NULL; - struct ec_node *cmd = NULL; - struct ec_node **table = NULL; - char *cmd_str = NULL; - size_t len = 0, i; - - /* retrieve config locally */ - expr = ec_config_dict_get(config, "expr"); - if (expr == NULL) { - errno = EINVAL; - goto fail; - } - - table = ec_node_config_node_list_to_table( - ec_config_dict_get(config, "children"), &len); - if (table == NULL) - goto fail; - - cmd_str = ec_strdup(expr->string); - if (cmd_str == NULL) - goto fail; - - /* parse expression to build the cmd child node */ - cmd = ec_node_cmd_build(node, cmd_str, table, len); - if (cmd == NULL) - goto fail; - - /* ok, store the config */ - ec_node_free(node->cmd); - node->cmd = cmd; - ec_free(node->cmd_str); - node->cmd_str = cmd_str; - for (i = 0; i < node->len; i++) - ec_node_free(node->table[i]); - ec_free(node->table); - node->table = table; - node->len = len; - - return 0; - -fail: - for (i = 0; i < len; i++) - ec_node_free(table[i]); - ec_free(table); - ec_free(cmd_str); - ec_node_free(cmd); - return -1; -} - -static size_t -ec_node_cmd_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; - - if (node->cmd == NULL) - return 0; - return 1; -} - -static int -ec_node_cmd_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; - - if (i > 0) - return -1; - - *child = node->cmd; - *refs = 1; - return 0; -} - -static struct ec_node_type ec_node_cmd_type = { - .name = "cmd", - .schema = ec_node_cmd_schema, - .set_config = ec_node_cmd_set_config, - .parse = ec_node_cmd_parse, - .complete = ec_node_cmd_complete, - .size = sizeof(struct ec_node_cmd), - .free_priv = ec_node_cmd_free_priv, - .get_children_count = ec_node_cmd_get_children_count, - .get_child = ec_node_cmd_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_cmd_type); - -struct ec_node *__ec_node_cmd(const char *id, const char *cmd, ...) -{ - struct ec_config *config = NULL, *children = NULL; - struct ec_node *gen_node = NULL; - struct ec_node_cmd *node = NULL; - va_list ap; - int ret; - - /* this block must stay first, it frees the nodes on error */ - va_start(ap, cmd); - children = ec_node_config_node_list_from_vargs(ap); - va_end(ap); - if (children == NULL) - goto fail; - - gen_node = ec_node_from_type(&ec_node_cmd_type, id); - if (gen_node == NULL) - goto fail; - node = (struct ec_node_cmd *)gen_node; - - node->expr = ec_node_cmd_build_expr(); - if (node->expr == NULL) - goto fail; - - node->parser = ec_node_cmd_build_parser(node->expr); - if (node->parser == NULL) - goto fail; - - config = ec_config_dict(); - if (config == NULL) - goto fail; - - if (ec_config_dict_set(config, "expr", ec_config_string(cmd)) < 0) - goto fail; - - if (ec_config_dict_set(config, "children", children) < 0) { - children = NULL; /* freed */ - goto fail; - } - children = NULL; - - ret = ec_node_set_config(gen_node, config); - config = NULL; /* freed */ - if (ret < 0) - goto fail; - - return gen_node; - -fail: - ec_node_free(gen_node); /* will also free added children */ - ec_config_free(children); - ec_config_free(config); - - return NULL; -} - -/* LCOV_EXCL_START */ -static int ec_node_cmd_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = EC_NODE_CMD(EC_NO_ID, - "command [option] (subset1, subset2, subset3, subset4) x|y z*", - ec_node_int("x", 0, 10, 10), - ec_node_int("y", 20, 30, 10) - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "subset1", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 4, "command", "subset3", "subset2", - "1"); - testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "subset2", "subset3", - "subset1", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 6, "command", "subset3", "subset1", - "subset4", "subset2", "4"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "23"); - testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "option", "23"); - testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "option", "23", - "z", "z"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "command", "15"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); - ec_node_free(node); - - node = EC_NODE_CMD(EC_NO_ID, "good morning [count] bob|bobby|michael", - ec_node_int("count", 0, 10, 10)); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 4, "good", "morning", "1", "bob"); - - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "good", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "g", EC_NODE_ENDLIST, - "good", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "good", "morning", "", EC_NODE_ENDLIST, - "bob", "bobby", "michael", EC_NODE_ENDLIST); - - ec_node_free(node); - - node = EC_NODE_CMD(EC_NO_ID, "[foo [bar]]"); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 0); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 0, "x"); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_cmd_test = { - .name = "node_cmd", - .test = ec_node_cmd_testcase, -}; - -EC_TEST_REGISTER(ec_node_cmd_test); diff --git a/lib/ecoli_node_cmd.h b/lib/ecoli_node_cmd.h deleted file mode 100644 index 99afc01..0000000 --- a/lib/ecoli_node_cmd.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_CMD_ -#define ECOLI_NODE_CMD_ - -#include - -#define EC_NODE_CMD(args...) __ec_node_cmd(args, EC_NODE_ENDLIST) - -struct ec_node *__ec_node_cmd(const char *id, const char *cmd_str, ...); - -#endif diff --git a/lib/ecoli_node_dynamic.c b/lib/ecoli_node_dynamic.c deleted file mode 100644 index 8a3edf3..0000000 --- a/lib/ecoli_node_dynamic.c +++ /dev/null @@ -1,191 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2017, Olivier MATZ - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ecoli_node_dynamic.h" - -EC_LOG_TYPE_REGISTER(node_dynamic); - -struct ec_node_dynamic { - struct ec_node gen; - ec_node_dynamic_build_t build; - void *opaque; -}; - -static int -ec_node_dynamic_parse(const struct ec_node *gen_node, - struct ec_parse *parse, - const struct ec_strvec *strvec) -{ - struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node; - struct ec_node *child = NULL; - void (*node_free)(struct ec_node *) = ec_node_free; - char key[64]; - int ret = -1; - - child = node->build(parse, node->opaque); - if (child == NULL) - goto fail; - - /* add the node pointer in the attributes, so it will be freed - * when parse is freed */ - snprintf(key, sizeof(key), "_dyn_%p", child); - ret = ec_keyval_set(ec_parse_get_attrs(parse), key, child, - (void *)node_free); - if (ret < 0) { - child = NULL; /* already freed */ - goto fail; - } - - return ec_node_parse_child(child, parse, strvec); - -fail: - ec_node_free(child); - return ret; -} - -static int -ec_node_dynamic_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node; - struct ec_parse *parse; - struct ec_node *child = NULL; - void (*node_free)(struct ec_node *) = ec_node_free; - char key[64]; - int ret = -1; - - parse = ec_comp_get_state(comp); - child = node->build(parse, node->opaque); - if (child == NULL) - goto fail; - - /* add the node pointer in the attributes, so it will be freed - * when parse is freed */ - snprintf(key, sizeof(key), "_dyn_%p", child); - ret = ec_keyval_set(comp->attrs, key, child, - (void *)node_free); - if (ret < 0) { - child = NULL; /* already freed */ - goto fail; - } - - return ec_node_complete_child(child, comp, strvec); - -fail: - ec_node_free(child); - return ret; -} - -static struct ec_node_type ec_node_dynamic_type = { - .name = "dynamic", - .parse = ec_node_dynamic_parse, - .complete = ec_node_dynamic_complete, - .size = sizeof(struct ec_node_dynamic), -}; - -struct ec_node * -ec_node_dynamic(const char *id, ec_node_dynamic_build_t build, void *opaque) -{ - struct ec_node *gen_node = NULL; - struct ec_node_dynamic *node; - - if (build == NULL) { - errno = EINVAL; - goto fail; - } - - gen_node = ec_node_from_type(&ec_node_dynamic_type, id); - if (gen_node == NULL) - goto fail; - - node = (struct ec_node_dynamic *)gen_node; - node->build = build; - node->opaque = opaque; - - return gen_node; - -fail: - ec_node_free(gen_node); - return NULL; - -} - -EC_NODE_TYPE_REGISTER(ec_node_dynamic_type); - -static struct ec_node * -build_counter(struct ec_parse *parse, void *opaque) -{ - const struct ec_node *node; - struct ec_parse *iter; - unsigned int count = 0; - char buf[32]; - - (void)opaque; - for (iter = ec_parse_get_root(parse); iter != NULL; - iter = ec_parse_iter_next(iter)) { - node = ec_parse_get_node(iter); - if (node->id && !strcmp(node->id, "my-id")) - count++; - } - snprintf(buf, sizeof(buf), "count-%u", count); - - return ec_node_str("my-id", buf); -} - -/* LCOV_EXCL_START */ -static int ec_node_dynamic_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node_many(EC_NO_ID, - ec_node_dynamic(EC_NO_ID, build_counter, NULL), - 1, 3); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0"); - testres |= EC_TEST_CHECK_PARSE(node, 3, "count-0", "count-1", "count-2"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0", "count-0"); - - /* test completion */ - - testres |= EC_TEST_CHECK_COMPLETE(node, - "c", EC_NODE_ENDLIST, - "count-0", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "count-0", "", EC_NODE_ENDLIST, - "count-1", EC_NODE_ENDLIST, - "count-1"); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_dynamic_test = { - .name = "node_dynamic", - .test = ec_node_dynamic_testcase, -}; - -EC_TEST_REGISTER(ec_node_dynamic_test); diff --git a/lib/ecoli_node_dynamic.h b/lib/ecoli_node_dynamic.h deleted file mode 100644 index 4f2535e..0000000 --- a/lib/ecoli_node_dynamic.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2017, Olivier MATZ - */ - -#ifndef ECOLI_NODE_DYNAMIC_ -#define ECOLI_NODE_DYNAMIC_ - -struct ec_node; -struct ec_parse; - -/* callback invoked by parse() or complete() to build the dynamic node - * the behavior of the node can depend on what is already parsed */ -typedef struct ec_node *(*ec_node_dynamic_build_t)( - struct ec_parse *state, void *opaque); - -struct ec_node *ec_node_dynamic(const char *id, ec_node_dynamic_build_t build, - void *opaque); - -#endif diff --git a/lib/ecoli_node_empty.c b/lib/ecoli_node_empty.c deleted file mode 100644 index 6ce76e8..0000000 --- a/lib/ecoli_node_empty.c +++ /dev/null @@ -1,83 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_empty); - -struct ec_node_empty { - struct ec_node gen; -}; - -static int ec_node_empty_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - (void)gen_node; - (void)state; - (void)strvec; - return 0; -} - -static struct ec_node_type ec_node_empty_type = { - .name = "empty", - .parse = ec_node_empty_parse, - .complete = ec_node_complete_unknown, - .size = sizeof(struct ec_node_empty), -}; - -EC_NODE_TYPE_REGISTER(ec_node_empty_type); - -/* LCOV_EXCL_START */ -static int ec_node_empty_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node("empty", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 0, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 0); - testres |= EC_TEST_CHECK_PARSE(node, 0, "foo", "bar"); - ec_node_free(node); - - /* never completes */ - node = ec_node("empty", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_empty_test = { - .name = "node_empty", - .test = ec_node_empty_testcase, -}; - -EC_TEST_REGISTER(ec_node_empty_test); diff --git a/lib/ecoli_node_empty.h b/lib/ecoli_node_empty.h deleted file mode 100644 index ed5e32e..0000000 --- a/lib/ecoli_node_empty.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * This node always matches an empty string vector - */ - -#ifndef ECOLI_NODE_EMPTY_ -#define ECOLI_NODE_EMPTY_ - -struct ec_node *ec_node_empty(const char *id); - -#endif diff --git a/lib/ecoli_node_expr.c b/lib/ecoli_node_expr.c deleted file mode 100644 index 3b47d8c..0000000 --- a/lib/ecoli_node_expr.c +++ /dev/null @@ -1,617 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_expr); - -struct ec_node_expr { - struct ec_node gen; - - /* the built node */ - struct ec_node *child; - - /* the configuration nodes */ - struct ec_node *val_node; - struct ec_node **bin_ops; - unsigned int bin_ops_len; - struct ec_node **pre_ops; - unsigned int pre_ops_len; - struct ec_node **post_ops; - unsigned int post_ops_len; - struct ec_node **open_ops; - struct ec_node **close_ops; - unsigned int paren_len; -}; - -static int ec_node_expr_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - - if (node->child == NULL) { - errno = ENOENT; - return -1; - } - - return ec_node_parse_child(node->child, state, strvec); -} - -static int -ec_node_expr_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - - if (node->child == NULL) { - errno = ENOENT; - return -1; - } - - return ec_node_complete_child(node->child, comp, strvec); -} - -static void ec_node_expr_free_priv(struct ec_node *gen_node) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - unsigned int i; - - ec_node_free(node->child); - ec_node_free(node->val_node); - - for (i = 0; i < node->bin_ops_len; i++) - ec_node_free(node->bin_ops[i]); - ec_free(node->bin_ops); - for (i = 0; i < node->pre_ops_len; i++) - ec_node_free(node->pre_ops[i]); - ec_free(node->pre_ops); - for (i = 0; i < node->post_ops_len; i++) - ec_node_free(node->post_ops[i]); - ec_free(node->post_ops); - for (i = 0; i < node->paren_len; i++) { - ec_node_free(node->open_ops[i]); - ec_node_free(node->close_ops[i]); - } - ec_free(node->open_ops); - ec_free(node->close_ops); -} - -static int ec_node_expr_build(struct ec_node_expr *node) -{ - struct ec_node *term = NULL, *expr = NULL, *next = NULL, - *pre_op = NULL, *post_op = NULL, *ref = NULL, - *post = NULL; - unsigned int i; - - ec_node_free(node->child); - node->child = NULL; - - if (node->val_node == NULL) { - errno = EINVAL; - return -1; - } - - if (node->bin_ops_len == 0 && node->pre_ops_len == 0 && - node->post_ops_len == 0) { - errno = EINVAL; - return -1; - } - - /* - * Example of created grammar: - * - * pre_op = "!" - * post_op = "^" - * post = val | - * pre_op expr | - * "(" expr ")" - * term = post post_op* - * prod = term ( "*" term )* - * sum = prod ( "+" prod )* - * expr = sum - */ - - /* we use this as a ref, will be set later */ - ref = ec_node("seq", "ref"); - if (ref == NULL) - return -1; - - /* prefix unary operators */ - pre_op = ec_node("or", "pre-op"); - if (pre_op == NULL) - goto fail; - for (i = 0; i < node->pre_ops_len; i++) { - if (ec_node_or_add(pre_op, ec_node_clone(node->pre_ops[i])) < 0) - goto fail; - } - - /* suffix unary operators */ - post_op = ec_node("or", "post-op"); - if (post_op == NULL) - goto fail; - for (i = 0; i < node->post_ops_len; i++) { - if (ec_node_or_add(post_op, ec_node_clone(node->post_ops[i])) < 0) - goto fail; - } - - post = ec_node("or", "post"); - if (post == NULL) - goto fail; - if (ec_node_or_add(post, ec_node_clone(node->val_node)) < 0) - goto fail; - if (ec_node_or_add(post, - EC_NODE_SEQ(EC_NO_ID, - ec_node_clone(pre_op), - ec_node_clone(ref))) < 0) - goto fail; - for (i = 0; i < node->paren_len; i++) { - if (ec_node_or_add(post, EC_NODE_SEQ(EC_NO_ID, - ec_node_clone(node->open_ops[i]), - ec_node_clone(ref), - ec_node_clone(node->close_ops[i]))) < 0) - goto fail; - } - term = EC_NODE_SEQ("term", - ec_node_clone(post), - ec_node_many(EC_NO_ID, ec_node_clone(post_op), 0, 0) - ); - if (term == NULL) - goto fail; - - for (i = 0; i < node->bin_ops_len; i++) { - next = EC_NODE_SEQ("next", - ec_node_clone(term), - ec_node_many(EC_NO_ID, - EC_NODE_SEQ(EC_NO_ID, - ec_node_clone(node->bin_ops[i]), - ec_node_clone(term) - ), - 0, 0 - ) - ); - ec_node_free(term); - term = next; - if (term == NULL) - goto fail; - } - expr = term; - term = NULL; - - /* free the initial references */ - ec_node_free(pre_op); - pre_op = NULL; - ec_node_free(post_op); - post_op = NULL; - ec_node_free(post); - post = NULL; - - if (ec_node_seq_add(ref, ec_node_clone(expr)) < 0) - goto fail; - ec_node_free(ref); - ref = NULL; - - node->child = expr; - - return 0; - -fail: - ec_node_free(term); - ec_node_free(expr); - ec_node_free(pre_op); - ec_node_free(post_op); - ec_node_free(post); - ec_node_free(ref); - - return -1; -} - -static size_t -ec_node_expr_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - - if (node->child) - return 1; - return 0; -} - -static int -ec_node_expr_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - - if (i >= 1) - return -1; - - *child = node->child; - *refs = 1; - return 0; -} - -static struct ec_node_type ec_node_expr_type = { - .name = "expr", - .parse = ec_node_expr_parse, - .complete = ec_node_expr_complete, - .size = sizeof(struct ec_node_expr), - .free_priv = ec_node_expr_free_priv, - .get_children_count = ec_node_expr_get_children_count, - .get_child = ec_node_expr_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_expr_type); - -int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - - if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) - goto fail; - - if (val_node == NULL) { - errno = EINVAL; - goto fail; - } - - ec_node_free(node->val_node); - node->val_node = val_node; - ec_node_expr_build(node); - - return 0; - -fail: - ec_node_free(val_node); - return -1; -} - -/* add a binary operator */ -int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - struct ec_node **bin_ops; - - if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) - goto fail; - - if (node == NULL || op == NULL) { - errno = EINVAL; - goto fail; - } - - bin_ops = ec_realloc(node->bin_ops, - (node->bin_ops_len + 1) * sizeof(*node->bin_ops)); - if (bin_ops == NULL) - goto fail;; - - node->bin_ops = bin_ops; - bin_ops[node->bin_ops_len] = op; - node->bin_ops_len++; - ec_node_expr_build(node); - - return 0; - -fail: - ec_node_free(op); - return -1; -} - -/* add a unary pre-operator */ -int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - struct ec_node **pre_ops; - - if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) - goto fail; - - if (node == NULL || op == NULL) { - errno = EINVAL; - goto fail; - } - - pre_ops = ec_realloc(node->pre_ops, - (node->pre_ops_len + 1) * sizeof(*node->pre_ops)); - if (pre_ops == NULL) - goto fail; - - node->pre_ops = pre_ops; - pre_ops[node->pre_ops_len] = op; - node->pre_ops_len++; - ec_node_expr_build(node); - - return 0; - -fail: - ec_node_free(op); - return -1; -} - -/* add a unary post-operator */ -int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - struct ec_node **post_ops; - - if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) - goto fail; - - if (node == NULL || op == NULL) { - errno = EINVAL; - goto fail; - } - - post_ops = ec_realloc(node->post_ops, - (node->post_ops_len + 1) * sizeof(*node->post_ops)); - if (post_ops == NULL) - goto fail; - - node->post_ops = post_ops; - post_ops[node->post_ops_len] = op; - node->post_ops_len++; - ec_node_expr_build(node); - - return 0; - -fail: - ec_node_free(op); - return -1; -} - -/* add parenthesis symbols */ -int ec_node_expr_add_parenthesis(struct ec_node *gen_node, - struct ec_node *open, struct ec_node *close) -{ - struct ec_node_expr *node = (struct ec_node_expr *)gen_node; - struct ec_node **open_ops, **close_ops; - - if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) - goto fail; - - if (node == NULL || open == NULL || close == NULL) { - errno = EINVAL; - goto fail; - } - - open_ops = ec_realloc(node->open_ops, - (node->paren_len + 1) * sizeof(*node->open_ops)); - if (open_ops == NULL) - goto fail; - close_ops = ec_realloc(node->close_ops, - (node->paren_len + 1) * sizeof(*node->close_ops)); - if (close_ops == NULL) - goto fail; - - node->open_ops = open_ops; - node->close_ops = close_ops; - open_ops[node->paren_len] = open; - close_ops[node->paren_len] = close; - node->paren_len++; - ec_node_expr_build(node); - - return 0; - -fail: - ec_node_free(open); - ec_node_free(close); - return -1; -} - -enum expr_node_type { - NONE, - VAL, - BIN_OP, - PRE_OP, - POST_OP, - PAREN_OPEN, - PAREN_CLOSE, -}; - -static enum expr_node_type get_node_type(const struct ec_node *expr_gen_node, - const struct ec_node *check) -{ - struct ec_node_expr *expr_node = (struct ec_node_expr *)expr_gen_node; - size_t i; - - if (check == expr_node->val_node) - return VAL; - - for (i = 0; i < expr_node->bin_ops_len; i++) { - if (check == expr_node->bin_ops[i]) - return BIN_OP; - } - for (i = 0; i < expr_node->pre_ops_len; i++) { - if (check == expr_node->pre_ops[i]) - return PRE_OP; - } - for (i = 0; i < expr_node->post_ops_len; i++) { - if (check == expr_node->post_ops[i]) - return POST_OP; - } - - for (i = 0; i < expr_node->paren_len; i++) { - if (check == expr_node->open_ops[i]) - return PAREN_OPEN; - } - for (i = 0; i < expr_node->paren_len; i++) { - if (check == expr_node->close_ops[i]) - return PAREN_CLOSE; - } - - return NONE; -} - -struct result { - bool has_val; - void *val; - const struct ec_parse *op; - enum expr_node_type op_type; -}; - -/* merge x and y results in x */ -static int merge_results(void *userctx, - const struct ec_node_expr_eval_ops *ops, - struct result *x, const struct result *y) -{ - if (y->has_val == 0 && y->op == NULL) - return 0; - if (x->has_val == 0 && x->op == NULL) { - *x = *y; - return 0; - } - - if (x->has_val && y->has_val && y->op != NULL) { - if (y->op_type == BIN_OP) { - if (ops->eval_bin_op(&x->val, userctx, x->val, - y->op, y->val) < 0) - return -1; - - return 0; - } - } - - if (x->has_val == 0 && x->op != NULL && y->has_val && y->op == NULL) { - if (x->op_type == PRE_OP) { - if (ops->eval_pre_op(&x->val, userctx, y->val, - x->op) < 0) - return -1; - x->has_val = true; - x->op_type = NONE; - x->op = NULL; - return 0; - } else if (x->op_type == BIN_OP) { - x->val = y->val; - x->has_val = true; - return 0; - } - } - - if (x->has_val && x->op == NULL && y->has_val == 0 && y->op != NULL) { - if (ops->eval_post_op(&x->val, userctx, x->val, y->op) < 0) - return -1; - - return 0; - } - - assert(false); /* we should not get here */ - return -1; -} - -static int eval_expression(struct result *result, - void *userctx, - const struct ec_node_expr_eval_ops *ops, - const struct ec_node *expr_gen_node, - const struct ec_parse *parse) - -{ - struct ec_parse *open = NULL, *close = NULL; - struct result child_result; - struct ec_parse *child; - enum expr_node_type type; - - memset(result, 0, sizeof(*result)); - memset(&child_result, 0, sizeof(child_result)); - - type = get_node_type(expr_gen_node, ec_parse_get_node(parse)); - if (type == VAL) { - if (ops->eval_var(&result->val, userctx, parse) < 0) - goto fail; - result->has_val = 1; - } else if (type == PRE_OP || type == POST_OP || type == BIN_OP) { - result->op = parse; - result->op_type = type; - } - - EC_PARSE_FOREACH_CHILD(child, parse) { - - type = get_node_type(expr_gen_node, ec_parse_get_node(child)); - if (type == PAREN_OPEN) { - open = child; - continue; - } else if (type == PAREN_CLOSE) { - close = child; - continue; - } - - if (eval_expression(&child_result, userctx, ops, - expr_gen_node, child) < 0) - goto fail; - - if (merge_results(userctx, ops, result, &child_result) < 0) - goto fail; - - memset(&child_result, 0, sizeof(child_result)); - } - - if (open != NULL && close != NULL) { - if (ops->eval_parenthesis(&result->val, userctx, open, close, - result->val) < 0) - goto fail; - } - - return 0; - -fail: - if (result->has_val) - ops->eval_free(result->val, userctx); - if (child_result.has_val) - ops->eval_free(child_result.val, userctx); - memset(result, 0, sizeof(*result)); - - return -1; -} - -int ec_node_expr_eval(void **user_result, const struct ec_node *node, - struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops, - void *userctx) -{ - struct result result; - - if (ops == NULL || ops->eval_var == NULL || ops->eval_pre_op == NULL || - ops->eval_post_op == NULL || ops->eval_bin_op == NULL || - ops->eval_parenthesis == NULL || - ops->eval_free == NULL) { - errno = EINVAL; - return -1; - } - - if (ec_node_check_type(node, &ec_node_expr_type) < 0) - return -1; - - if (!ec_parse_matches(parse)) { - errno = EINVAL; - return -1; - } - - if (eval_expression(&result, userctx, ops, node, parse) < 0) - return -1; - - assert(result.has_val); - assert(result.op == NULL); - *user_result = result.val; - - return 0; -} - -/* the test case is in a separate file ecoli_node_expr_test.c */ diff --git a/lib/ecoli_node_expr.h b/lib/ecoli_node_expr.h deleted file mode 100644 index 4f21d81..0000000 --- a/lib/ecoli_node_expr.h +++ /dev/null @@ -1,93 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_EXPR_ -#define ECOLI_NODE_EXPR_ - -#include - -/** - * Callback function type for evaluating a variable - * - * @param result - * On success, this pointer must be set by the user to point - * to a user structure describing the evaluated result. - * @param userctx - * A user-defined context passed to all callback functions, which - * can be used to maintain a state or store global information. - * @param var - * The parse result referencing the variable. - * @return - * 0 on success (*result must be set), or -errno on error (*result - * is undefined). - */ -typedef int (*ec_node_expr_eval_var_t)( - void **result, void *userctx, - const struct ec_parse *var); - -/** - * Callback function type for evaluating a prefix-operator - * - * @param result - * On success, this pointer must be set by the user to point - * to a user structure describing the evaluated result. - * @param userctx - * A user-defined context passed to all callback functions, which - * can be used to maintain a state or store global information. - * @param operand - * The evaluated expression on which the operation should be applied. - * @param var - * The parse result referencing the operator. - * @return - * 0 on success (*result must be set, operand is freed), - * or -errno on error (*result is undefined, operand is not freed). - */ -typedef int (*ec_node_expr_eval_pre_op_t)( - void **result, void *userctx, - void *operand, - const struct ec_parse *operator); - -typedef int (*ec_node_expr_eval_post_op_t)( - void **result, void *userctx, - void *operand, - const struct ec_parse *operator); - -typedef int (*ec_node_expr_eval_bin_op_t)( - void **result, void *userctx, - void *operand1, - const struct ec_parse *operator, - void *operand2); - -typedef int (*ec_node_expr_eval_parenthesis_t)( - void **result, void *userctx, - const struct ec_parse *open_paren, - const struct ec_parse *close_paren, - void * value); - -typedef void (*ec_node_expr_eval_free_t)( - void *result, void *userctx); - - -struct ec_node *ec_node_expr(const char *id); -int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node); -int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op); -int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op); -int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op); -int ec_node_expr_add_parenthesis(struct ec_node *gen_node, - struct ec_node *open, struct ec_node *close); - -struct ec_node_expr_eval_ops { - ec_node_expr_eval_var_t eval_var; - ec_node_expr_eval_pre_op_t eval_pre_op; - ec_node_expr_eval_post_op_t eval_post_op; - ec_node_expr_eval_bin_op_t eval_bin_op; - ec_node_expr_eval_parenthesis_t eval_parenthesis; - ec_node_expr_eval_free_t eval_free; -}; - -int ec_node_expr_eval(void **result, const struct ec_node *node, - struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops, - void *userctx); - -#endif diff --git a/lib/ecoli_node_expr_test.c b/lib/ecoli_node_expr_test.c deleted file mode 100644 index 93e33a4..0000000 --- a/lib/ecoli_node_expr_test.c +++ /dev/null @@ -1,308 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_expr); - -struct my_eval_result { - int val; -}; - -static int -ec_node_expr_test_eval_var(void **result, void *userctx, - const struct ec_parse *var) -{ - const struct ec_strvec *vec; - const struct ec_node *node; - struct my_eval_result *eval = NULL; - int64_t val; - - (void)userctx; - - /* get parsed string vector, it should contain only one str */ - vec = ec_parse_strvec(var); - if (ec_strvec_len(vec) != 1) { - errno = EINVAL; - return -1; - } - - node = ec_parse_get_node(var); - if (ec_node_int_getval(node, ec_strvec_val(vec, 0), &val) < 0) - return -1; - - eval = ec_malloc(sizeof(*eval)); - if (eval == NULL) - return -1; - - eval->val = val; - EC_LOG(EC_LOG_DEBUG, "eval var %d\n", eval->val); - *result = eval; - - return 0; -} - -static int -ec_node_expr_test_eval_pre_op(void **result, void *userctx, void *operand, - const struct ec_parse *operator) -{ - const struct ec_strvec *vec; - struct my_eval_result *eval = operand;; - - (void)userctx; - - /* get parsed string vector, it should contain only one str */ - vec = ec_parse_strvec(operator); - if (ec_strvec_len(vec) != 1) { - errno = EINVAL; - return -1; - } - - if (!strcmp(ec_strvec_val(vec, 0), "!")) { - eval->val = !eval->val; - } else { - errno = EINVAL; - return -1; - } - - - EC_LOG(EC_LOG_DEBUG, "eval pre_op %d\n", eval->val); - *result = eval; - - return 0; -} - -static int -ec_node_expr_test_eval_post_op(void **result, void *userctx, void *operand, - const struct ec_parse *operator) -{ - const struct ec_strvec *vec; - struct my_eval_result *eval = operand;; - - (void)userctx; - - /* get parsed string vector, it should contain only one str */ - vec = ec_parse_strvec(operator); - if (ec_strvec_len(vec) != 1) { - errno = EINVAL; - return -1; - } - - if (!strcmp(ec_strvec_val(vec, 0), "^")) { - eval->val = eval->val * eval->val; - } else { - errno = EINVAL; - return -1; - } - - EC_LOG(EC_LOG_DEBUG, "eval post_op %d\n", eval->val); - *result = eval; - - return 0; -} - -static int -ec_node_expr_test_eval_bin_op(void **result, void *userctx, void *operand1, - const struct ec_parse *operator, void *operand2) - -{ - const struct ec_strvec *vec; - struct my_eval_result *eval1 = operand1;; - struct my_eval_result *eval2 = operand2;; - - (void)userctx; - - /* get parsed string vector, it should contain only one str */ - vec = ec_parse_strvec(operator); - if (ec_strvec_len(vec) != 1) { - errno = EINVAL; - return -1; - } - - if (!strcmp(ec_strvec_val(vec, 0), "+")) { - eval1->val = eval1->val + eval2->val; - } else if (!strcmp(ec_strvec_val(vec, 0), "*")) { - eval1->val = eval1->val * eval2->val; - } else { - errno = EINVAL; - return -1; - } - - EC_LOG(EC_LOG_DEBUG, "eval bin_op %d\n", eval1->val); - ec_free(eval2); - *result = eval1; - - return 0; -} - -static int -ec_node_expr_test_eval_parenthesis(void **result, void *userctx, - const struct ec_parse *open_paren, - const struct ec_parse *close_paren, - void *value) -{ - (void)userctx; - (void)open_paren; - (void)close_paren; - - EC_LOG(EC_LOG_DEBUG, "eval paren\n"); - *result = value; - - return 0; -} - -static void -ec_node_expr_test_eval_free(void *result, void *userctx) -{ - (void)userctx; - ec_free(result); -} - -static const struct ec_node_expr_eval_ops test_ops = { - .eval_var = ec_node_expr_test_eval_var, - .eval_pre_op = ec_node_expr_test_eval_pre_op, - .eval_post_op = ec_node_expr_test_eval_post_op, - .eval_bin_op = ec_node_expr_test_eval_bin_op, - .eval_parenthesis = ec_node_expr_test_eval_parenthesis, - .eval_free = ec_node_expr_test_eval_free, -}; - -static int ec_node_expr_test_eval(struct ec_node *lex_node, - const struct ec_node *expr_node, - const char *str, int val) -{ - struct ec_parse *p; - void *result; - struct my_eval_result *eval; - int ret; - - p = ec_node_parse(lex_node, str); - if (p == NULL) - return -1; - - ret = ec_node_expr_eval(&result, expr_node, p, &test_ops, NULL); - ec_parse_free(p); - if (ret < 0) - return -1; - - /* the parse value is an integer */ - eval = result; - assert(eval != NULL); - - EC_LOG(EC_LOG_DEBUG, "result: %d (expected %d)\n", eval->val, val); - if (eval->val == val) - ret = 0; - else - ret = -1; - - ec_free(eval); - - return ret; -} - -/* LCOV_EXCL_START */ -static int ec_node_expr_testcase(void) -{ - struct ec_node *node = NULL, *lex_node = NULL; - int testres = 0; - - node = ec_node("expr", "my_expr"); - if (node == NULL) - return -1; - - ec_node_expr_set_val_node(node, ec_node_int(EC_NO_ID, 0, UCHAR_MAX, 0)); - ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "+")); - ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "*")); - ec_node_expr_add_pre_op(node, ec_node_str(EC_NO_ID, "!")); /* not */ - ec_node_expr_add_post_op(node, ec_node_str(EC_NO_ID, "^")); /* square */ - ec_node_expr_add_parenthesis(node, ec_node_str(EC_NO_ID, "("), - ec_node_str(EC_NO_ID, ")")); - testres |= EC_TEST_CHECK_PARSE(node, 1, "1"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "*"); - testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1", "*"); - testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "+", "!", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "^", "+", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "*", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "+", "1"); - testres |= EC_TEST_CHECK_PARSE(node, 7, "1", "*", "1", "*", "1", "*", - "1"); - testres |= EC_TEST_CHECK_PARSE( - node, 10, "!", "(", "1", "*", "(", "1", "+", "1", ")", ")"); - testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "+", "!", "1", "^"); - - /* prepend a lexer to the expression node */ - lex_node = ec_node_re_lex(EC_NO_ID, ec_node_clone(node)); - if (lex_node == NULL) - goto fail; - - testres |= ec_node_re_lex_add(lex_node, "[0-9]+", 1); /* vars */ - testres |= ec_node_re_lex_add(lex_node, "[+*!^()]", 1); /* operators */ - testres |= ec_node_re_lex_add(lex_node, "[ ]+", 0); /* spaces */ - - /* valid expressions */ - testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!1"); - testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^"); - testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^ + 1"); - testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1 + 4 * (2 + 3^)^"); - testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1)"); - testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "3*!3+!3*(2+ 2)"); - testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!!(!1)^ + !(4 + (2*3))"); - testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1 + 1)^ * 1^"); - - /* invalid expressions */ - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, ""); - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "()"); - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "("); - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, ")"); - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "+1"); - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+"); - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+*1"); - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+(1*1"); - testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+!1!1)"); - - testres |= ec_node_expr_test_eval(lex_node, node, "1^", 1); - testres |= ec_node_expr_test_eval(lex_node, node, "2^", 4); - testres |= ec_node_expr_test_eval(lex_node, node, "!1", 0); - testres |= ec_node_expr_test_eval(lex_node, node, "!0", 1); - - testres |= ec_node_expr_test_eval(lex_node, node, "1+1", 2); - testres |= ec_node_expr_test_eval(lex_node, node, "1+2+3", 6); - testres |= ec_node_expr_test_eval(lex_node, node, "1+1*2", 4); - testres |= ec_node_expr_test_eval(lex_node, node, "2 * 2^", 8); - testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !0)^ * !0^", 4); - testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !1) * 3", 3); - - ec_node_free(node); - ec_node_free(lex_node); - - return testres; - -fail: - ec_node_free(lex_node); - ec_node_free(node); - return -1; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_expr_test = { - .name = "node_expr", - .test = ec_node_expr_testcase, -}; - -EC_TEST_REGISTER(ec_node_expr_test); diff --git a/lib/ecoli_node_file.c b/lib/ecoli_node_file.c deleted file mode 100644 index 001dcb6..0000000 --- a/lib/ecoli_node_file.c +++ /dev/null @@ -1,425 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_file); - -struct ec_node_file { - struct ec_node gen; - - /* below functions pointers are only useful for test */ - int (*lstat)(const char *pathname, struct stat *buf); - DIR *(*opendir)(const char *name); - struct dirent *(*readdir)(DIR *dirp); - int (*closedir)(DIR *dirp); - int (*dirfd)(DIR *dirp); - int (*fstatat)(int dirfd, const char *pathname, struct stat *buf, - int flags); -}; - -static int -ec_node_file_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - (void)gen_node; - (void)state; - - if (ec_strvec_len(strvec) == 0) - return EC_PARSE_NOMATCH; - - return 1; -} - -/* - * Almost the same than dirname (3) and basename (3) except that: - * - it always returns a substring of the given path, which can - * be empty. - * - the behavior is different when the path finishes with a '/' - * - the path argument is not modified - * - the outputs are allocated and must be freed with ec_free(). - * - * path dirname basename split_path - * /usr/lib /usr lib /usr/ lib - * /usr/ / usr /usr/ - * usr . usr usr - * / / / / - * . . . . - * .. . .. .. - */ -static int split_path(const char *path, char **dname_p, char **bname_p) -{ - char *last_slash; - size_t dirlen; - char *dname, *bname; - - *dname_p = NULL; - *bname_p = NULL; - - last_slash = strrchr(path, '/'); - if (last_slash == NULL) - dirlen = 0; - else - dirlen = last_slash - path + 1; - - dname = ec_strdup(path); - if (dname == NULL) - return -1; - dname[dirlen] = '\0'; - - bname = ec_strdup(path + dirlen); - if (bname == NULL) { - ec_free(dname); - return -1; - } - - *dname_p = dname; - *bname_p = bname; - - return 0; -} - -static int -ec_node_file_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_file *node = (struct ec_node_file *)gen_node; - char *dname = NULL, *bname = NULL, *effective_dir; - struct ec_comp_item *item = NULL; - enum ec_comp_type type; - struct stat st, st2; - const char *input; - size_t bname_len; - struct dirent *de = NULL; - DIR *dir = NULL; - char *comp_str = NULL; - char *disp_str = NULL; - int is_dir = 0; - - /* - * Example with this file tree: - * / - * ├── dir1 - * │   ├── file1 - * │   ├── file2 - * │   └── subdir - * │   └── file3 - * ├── dir2 - * │   └── file4 - * └── file5 - * - * Input Output completions - * / [dir1/, dir2/, file5] - * /d [dir1/, dir2/] - * /f [file5] - * /dir1/ [file1, file2, subdir/] - * - * - * - */ - - if (ec_strvec_len(strvec) != 1) - return 0; - - input = ec_strvec_val(strvec, 0); - if (split_path(input, &dname, &bname) < 0) - return -1; - - if (strcmp(dname, "") == 0) - effective_dir = "."; - else - effective_dir = dname; - - if (node->lstat(effective_dir, &st) < 0) - goto fail; - if (!S_ISDIR(st.st_mode)) - goto out; - - dir = node->opendir(effective_dir); - if (dir == NULL) - goto fail; - - bname_len = strlen(bname); - while (1) { - int save_errno = errno; - - errno = 0; - de = node->readdir(dir); - if (de == NULL) { - if (errno == 0) { - errno = save_errno; - goto out; - } else { - goto fail; - } - } - - if (!ec_str_startswith(de->d_name, bname)) - continue; - if (bname[0] != '.' && de->d_name[0] == '.') - continue; - - /* add '/' if it's a dir */ - if (de->d_type == DT_DIR) { - is_dir = 1; - } else if (de->d_type == DT_UNKNOWN) { - int dir_fd = node->dirfd(dir); - - if (dir_fd < 0) - goto fail; - if (node->fstatat(dir_fd, de->d_name, &st2, 0) < 0) - goto fail; - if (S_ISDIR(st2.st_mode)) - is_dir = 1; - else - is_dir = 0; - } else { - is_dir = 0; - } - - if (is_dir) { - type = EC_COMP_PARTIAL; - if (ec_asprintf(&comp_str, "%s%s/", input, - &de->d_name[bname_len]) < 0) - goto fail; - if (ec_asprintf(&disp_str, "%s/", de->d_name) < 0) - goto fail; - } else { - type = EC_COMP_FULL; - if (ec_asprintf(&comp_str, "%s%s", input, - &de->d_name[bname_len]) < 0) - goto fail; - if (ec_asprintf(&disp_str, "%s", de->d_name) < 0) - goto fail; - } - if (ec_comp_add_item(comp, gen_node, &item, - type, input, comp_str) < 0) - goto out; - - /* fix the display string: we don't want to display the full - * path. */ - if (ec_comp_item_set_display(item, disp_str) < 0) - goto out; - - item = NULL; - ec_free(comp_str); - comp_str = NULL; - ec_free(disp_str); - disp_str = NULL; - } -out: - ec_free(comp_str); - ec_free(disp_str); - ec_free(dname); - ec_free(bname); - if (dir != NULL) - node->closedir(dir); - - return 0; - -fail: - ec_free(comp_str); - ec_free(disp_str); - ec_free(dname); - ec_free(bname); - if (dir != NULL) - node->closedir(dir); - - return -1; -} - -static int -ec_node_file_init_priv(struct ec_node *gen_node) -{ - struct ec_node_file *node = (struct ec_node_file *)gen_node; - - node->lstat = lstat; - node->opendir = opendir; - node->readdir = readdir; - node->dirfd = dirfd; - node->fstatat = fstatat; - - return 0; -} - -static struct ec_node_type ec_node_file_type = { - .name = "file", - .parse = ec_node_file_parse, - .complete = ec_node_file_complete, - .size = sizeof(struct ec_node_file), - .init_priv = ec_node_file_init_priv, -}; - -EC_NODE_TYPE_REGISTER(ec_node_file_type); - -/* LCOV_EXCL_START */ -static int -test_lstat(const char *pathname, struct stat *buf) -{ - if (!strcmp(pathname, "/tmp/toto/")) { - struct stat st = { .st_mode = S_IFDIR }; - memcpy(buf, &st, sizeof(*buf)); - return 0; - } - - errno = ENOENT; - return -1; -} - -static DIR * -test_opendir(const char *name) -{ - int *p; - - if (strcmp(name, "/tmp/toto/")) { - errno = ENOENT; - return NULL; - } - - p = malloc(sizeof(int)); - if (p) - *p = 0; - - return (DIR *)p; -} - -static struct dirent * -test_readdir(DIR *dirp) -{ - static struct dirent de[] = { - { .d_type = DT_DIR, .d_name = ".." }, - { .d_type = DT_DIR, .d_name = "." }, - { .d_type = DT_REG, .d_name = "bar" }, - { .d_type = DT_UNKNOWN, .d_name = "bar2" }, - { .d_type = DT_REG, .d_name = "foo" }, - { .d_type = DT_DIR, .d_name = "titi" }, - { .d_type = DT_UNKNOWN, .d_name = "tutu" }, - { .d_name = "" }, - }; - int *p = (int *)dirp; - struct dirent *ret = &de[*p]; - - if (!strcmp(ret->d_name, "")) - return NULL; - - *p = *p + 1; - - return ret; -} - -static int -test_closedir(DIR *dirp) -{ - free(dirp); - return 0; -} - -static int -test_dirfd(DIR *dirp) -{ - int *p = (int *)dirp; - return *p; -} - -static int -test_fstatat(int dirfd, const char *pathname, struct stat *buf, - int flags) -{ - (void)dirfd; - (void)flags; - - if (!strcmp(pathname, "bar2")) { - struct stat st = { .st_mode = S_IFREG }; - memcpy(buf, &st, sizeof(*buf)); - return 0; - } else if (!strcmp(pathname, "tutu")) { - struct stat st = { .st_mode = S_IFDIR }; - memcpy(buf, &st, sizeof(*buf)); - return 0; - } - - errno = ENOENT; - return -1; -} - -static int -ec_node_file_override_functions(struct ec_node *gen_node) -{ - struct ec_node_file *node = (struct ec_node_file *)gen_node; - - node->lstat = test_lstat; - node->opendir = test_opendir; - node->readdir = test_readdir; - node->closedir = test_closedir; - node->dirfd = test_dirfd; - node->fstatat = test_fstatat; - - return 0; -} - -static int ec_node_file_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node("file", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - ec_node_file_override_functions(node); - - /* any string matches */ - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1); - - /* test completion */ - testres |= EC_TEST_CHECK_COMPLETE(node, - EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "/tmp/toto/t", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE_PARTIAL(node, - "/tmp/toto/t", EC_NODE_ENDLIST, - "/tmp/toto/titi/", "/tmp/toto/tutu/", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "/tmp/toto/f", EC_NODE_ENDLIST, - "/tmp/toto/foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "/tmp/toto/b", EC_NODE_ENDLIST, - "/tmp/toto/bar", "/tmp/toto/bar2", EC_NODE_ENDLIST); - - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_file_test = { - .name = "node_file", - .test = ec_node_file_testcase, -}; - -EC_TEST_REGISTER(ec_node_file_test); diff --git a/lib/ecoli_node_file.h b/lib/ecoli_node_file.h deleted file mode 100644 index 5760902..0000000 --- a/lib/ecoli_node_file.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_FILE_ -#define ECOLI_NODE_FILE_ - -#include - -struct ec_node *ec_node_file(const char *id, const char *file); - -/* file is duplicated */ -int ec_node_file_set_str(struct ec_node *node, const char *file); - -#endif diff --git a/lib/ecoli_node_helper.c b/lib/ecoli_node_helper.c deleted file mode 100644 index 9ec7e89..0000000 --- a/lib/ecoli_node_helper.c +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -struct ec_node ** -ec_node_config_node_list_to_table(const struct ec_config *config, - size_t *len) -{ - struct ec_node **table = NULL; - struct ec_config *child; - size_t n, i; - - *len = 0; - - if (config == NULL) { - errno = EINVAL; - return NULL; - } - - if (ec_config_get_type(config) != EC_CONFIG_TYPE_LIST) { - errno = EINVAL; - return NULL; - } - - n = 0; - TAILQ_FOREACH(child, &config->list, next) { - if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { - errno = EINVAL; - return NULL; - } - n++; - } - - table = ec_malloc(n * sizeof(*table)); - if (table == NULL) - goto fail; - - n = 0; - TAILQ_FOREACH(child, &config->list, next) { - table[n] = ec_node_clone(child->node); - n++; - } - - *len = n; - - return table; - -fail: - if (table != NULL) { - for (i = 0; i < n; i++) - ec_node_free(table[i]); - } - ec_free(table); - - return NULL; -} - -struct ec_config * -ec_node_config_node_list_from_vargs(va_list ap) -{ - struct ec_config *list = NULL; - struct ec_node *node = va_arg(ap, struct ec_node *); - - list = ec_config_list(); - if (list == NULL) - goto fail; - - for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *)) { - if (node == NULL) - goto fail; - - if (ec_config_list_add(list, ec_config_node(node)) < 0) - goto fail; - } - - return list; - -fail: - for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *)) - ec_node_free(node); - ec_config_free(list); - - return NULL; -} diff --git a/lib/ecoli_node_helper.h b/lib/ecoli_node_helper.h deleted file mode 100644 index 9dbf519..0000000 --- a/lib/ecoli_node_helper.h +++ /dev/null @@ -1,58 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -/** - * Helpers that are commonly used in nodes. - */ - -#ifndef ECOLI_NODE_HELPERS_ -#define ECOLI_NODE_HELPERS - -struct ec_node; - -/** - * Build a node table from a node list in a ec_config. - * - * The function takes a node configuration as parameter, which must be a - * node list. From it, a node table is built. A reference is taken for - * each node. - * - * On error, no reference is taken. - * - * @param config - * The configuration (type must be a list of nodes). If it is - * NULL, an error is returned. - * @param len - * The length of the allocated table on success, or 0 on error. - * @return - * The allocated node table, that must be freed by the caller: - * each entry must be freed with ec_node_free() and the table - * with ec_free(). On error, NULL is returned and errno is set. - */ -struct ec_node ** -ec_node_config_node_list_to_table(const struct ec_config *config, - size_t *len); - -/** - * Build a list of config nodes from variable arguments. - * - * The va_list argument is a list of pointer to ec_node structures, - * terminated with EC_NODE_ENDLIST. - * - * This helper is used by nodes that contain a list of nodes, - * like "seq", "or", ... - * - * @param ap - * List of pointer to ec_node structures, terminated with - * EC_NODE_ENDLIST. - * @return - * A pointer to an ec_config structure. In this case, the - * nodes will be freed when the config structure will be freed. - * On error, NULL is returned (and errno is set), and the - * nodes are freed. - */ -struct ec_config * -ec_node_config_node_list_from_vargs(va_list ap); - -#endif diff --git a/lib/ecoli_node_int.c b/lib/ecoli_node_int.c deleted file mode 100644 index 9b56e22..0000000 --- a/lib/ecoli_node_int.c +++ /dev/null @@ -1,501 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_int); - -/* common to int and uint */ -struct ec_node_int_uint { - struct ec_node gen; - bool is_signed; - bool check_min; - bool check_max; - union { - int64_t min; - uint64_t umin; - }; - union { - int64_t max; - uint64_t umax; - }; - unsigned int base; -}; - -/* XXX to utils.c ? */ -static int parse_llint(struct ec_node_int_uint *node, const char *str, - int64_t *val) -{ - char *endptr; - int save_errno = errno; - - errno = 0; - *val = strtoll(str, &endptr, node->base); - - if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) || - (errno != 0 && *val == 0)) - return -1; - - if (node->check_min && *val < node->min) { - errno = ERANGE; - return -1; - } - - if (node->check_max && *val > node->max) { - errno = ERANGE; - return -1; - } - - if (*endptr != 0) { - errno = EINVAL; - return -1; - } - - errno = save_errno; - return 0; -} - -static int parse_ullint(struct ec_node_int_uint *node, const char *str, - uint64_t *val) -{ - char *endptr; - int save_errno = errno; - - /* since a negative input is silently converted to a positive - * one by strtoull(), first check that it is positive */ - if (strchr(str, '-')) - return -1; - - errno = 0; - *val = strtoull(str, &endptr, node->base); - - if ((errno == ERANGE && *val == ULLONG_MAX) || - (errno != 0 && *val == 0)) - return -1; - - if (node->check_min && *val < node->umin) - return -1; - - if (node->check_max && *val > node->umax) - return -1; - - if (*endptr != 0) - return -1; - - errno = save_errno; - return 0; -} - -static int ec_node_int_uint_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; - const char *str; - uint64_t u64; - int64_t i64; - - (void)state; - - if (ec_strvec_len(strvec) == 0) - return EC_PARSE_NOMATCH; - - str = ec_strvec_val(strvec, 0); - if (node->is_signed) { - if (parse_llint(node, str, &i64) < 0) - return EC_PARSE_NOMATCH; - } else { - if (parse_ullint(node, str, &u64) < 0) - return EC_PARSE_NOMATCH; - } - return 1; -} - -static int -ec_node_uint_init_priv(struct ec_node *gen_node) -{ - struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; - - node->is_signed = true; - - return 0; -} - -static const struct ec_config_schema ec_node_int_schema[] = { - { - .key = "min", - .desc = "The minimum valid value (included).", - .type = EC_CONFIG_TYPE_INT64, - }, - { - .key = "max", - .desc = "The maximum valid value (included).", - .type = EC_CONFIG_TYPE_INT64, - }, - { - .key = "base", - .desc = "The base to use. If unset or 0, try to guess.", - .type = EC_CONFIG_TYPE_UINT64, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_int_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; - const struct ec_config *min_value = NULL; - const struct ec_config *max_value = NULL; - const struct ec_config *base_value = NULL; - char *s = NULL; - - min_value = ec_config_dict_get(config, "min"); - max_value = ec_config_dict_get(config, "max"); - base_value = ec_config_dict_get(config, "base"); - - if (min_value && max_value && min_value->i64 > max_value->i64) { - errno = EINVAL; - goto fail; - } - - if (min_value != NULL) { - node->check_min = true; - node->min = min_value->i64; - } else { - node->check_min = false; - } - if (max_value != NULL) { - node->check_max = true; - node->max = max_value->i64; - } else { - node->check_min = false; - } - if (base_value != NULL) - node->base = base_value->u64; - else - node->base = 0; - - return 0; - -fail: - ec_free(s); - return -1; -} - -static struct ec_node_type ec_node_int_type = { - .name = "int", - .schema = ec_node_int_schema, - .set_config = ec_node_int_set_config, - .parse = ec_node_int_uint_parse, - .complete = ec_node_complete_unknown, - .size = sizeof(struct ec_node_int_uint), - .init_priv = ec_node_uint_init_priv, -}; - -EC_NODE_TYPE_REGISTER(ec_node_int_type); - -struct ec_node *ec_node_int(const char *id, int64_t min, - int64_t max, unsigned int base) -{ - struct ec_config *config = NULL; - struct ec_node *gen_node = NULL; - int ret; - - gen_node = ec_node_from_type(&ec_node_int_type, id); - if (gen_node == NULL) - return NULL; - - config = ec_config_dict(); - if (config == NULL) - goto fail; - - ret = ec_config_dict_set(config, "min", ec_config_i64(min)); - if (ret < 0) - goto fail; - ret = ec_config_dict_set(config, "max", ec_config_i64(max)); - if (ret < 0) - goto fail; - ret = ec_config_dict_set(config, "base", ec_config_u64(base)); - if (ret < 0) - goto fail; - - ret = ec_node_set_config(gen_node, config); - config = NULL; - if (ret < 0) - goto fail; - - return gen_node; - -fail: - ec_config_free(config); - ec_node_free(gen_node); - return NULL; -} - -static const struct ec_config_schema ec_node_uint_schema[] = { - { - .key = "min", - .desc = "The minimum valid value (included).", - .type = EC_CONFIG_TYPE_UINT64, - }, - { - .key = "max", - .desc = "The maximum valid value (included).", - .type = EC_CONFIG_TYPE_UINT64, - }, - { - .key = "base", - .desc = "The base to use. If unset or 0, try to guess.", - .type = EC_CONFIG_TYPE_UINT64, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_uint_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; - const struct ec_config *min_value = NULL; - const struct ec_config *max_value = NULL; - const struct ec_config *base_value = NULL; - char *s = NULL; - - min_value = ec_config_dict_get(config, "min"); - max_value = ec_config_dict_get(config, "max"); - base_value = ec_config_dict_get(config, "base"); - - if (min_value && max_value && min_value->u64 > max_value->u64) { - errno = EINVAL; - goto fail; - } - - if (min_value != NULL) { - node->check_min = true; - node->min = min_value->u64; - } else { - node->check_min = false; - } - if (max_value != NULL) { - node->check_max = true; - node->max = max_value->u64; - } else { - node->check_min = false; - } - if (base_value != NULL) - node->base = base_value->u64; - else - node->base = 0; - - return 0; - -fail: - ec_free(s); - return -1; -} - -static struct ec_node_type ec_node_uint_type = { - .name = "uint", - .schema = ec_node_uint_schema, - .set_config = ec_node_uint_set_config, - .parse = ec_node_int_uint_parse, - .complete = ec_node_complete_unknown, - .size = sizeof(struct ec_node_int_uint), -}; - -EC_NODE_TYPE_REGISTER(ec_node_uint_type); - -struct ec_node *ec_node_uint(const char *id, uint64_t min, - uint64_t max, unsigned int base) -{ - struct ec_config *config = NULL; - struct ec_node *gen_node = NULL; - int ret; - - gen_node = ec_node_from_type(&ec_node_uint_type, id); - if (gen_node == NULL) - return NULL; - - config = ec_config_dict(); - if (config == NULL) - goto fail; - - ret = ec_config_dict_set(config, "min", ec_config_u64(min)); - if (ret < 0) - goto fail; - ret = ec_config_dict_set(config, "max", ec_config_u64(max)); - if (ret < 0) - goto fail; - ret = ec_config_dict_set(config, "base", ec_config_u64(base)); - if (ret < 0) - goto fail; - - ret = ec_node_set_config(gen_node, config); - config = NULL; - if (ret < 0) - goto fail; - - return gen_node; - -fail: - ec_config_free(config); - ec_node_free(gen_node); - return NULL; -} - -int ec_node_int_getval(const struct ec_node *gen_node, const char *str, - int64_t *result) -{ - struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; - int ret; - - ret = ec_node_check_type(gen_node, &ec_node_int_type); - if (ret < 0) - return ret; - - if (parse_llint(node, str, result) < 0) - return -1; - - return 0; -} - -int ec_node_uint_getval(const struct ec_node *gen_node, const char *str, - uint64_t *result) -{ - struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; - int ret; - - ret = ec_node_check_type(gen_node, &ec_node_uint_type); - if (ret < 0) - return ret; - - if (parse_ullint(node, str, result) < 0) - return -1; - - return 0; -} - -/* LCOV_EXCL_START */ -static int ec_node_int_testcase(void) -{ - struct ec_parse *p; - struct ec_node *node; - const char *s; - int testres = 0; - uint64_t u64; - int64_t i64; - - node = ec_node_uint(EC_NO_ID, 1, 256, 0); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, -1, ""); - testres |= EC_TEST_CHECK_PARSE(node, -1, "0"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "1"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "256", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "0x100"); - testres |= EC_TEST_CHECK_PARSE(node, 1, " 1"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "-1"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "0x101"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "0x100000000000000000"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "4r"); - - p = ec_node_parse(node, "1"); - s = ec_strvec_val(ec_parse_strvec(p), 0); - testres |= EC_TEST_CHECK(s != NULL && - ec_node_uint_getval(node, s, &u64) == 0 && - u64 == 1, "bad integer value"); - ec_parse_free(p); - - p = ec_node_parse(node, "10"); - s = ec_strvec_val(ec_parse_strvec(p), 0); - testres |= EC_TEST_CHECK(s != NULL && - ec_node_uint_getval(node, s, &u64) == 0 && - u64 == 10, "bad integer value"); - ec_parse_free(p); - ec_node_free(node); - - node = ec_node_int(EC_NO_ID, -1, LLONG_MAX, 16); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "0"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "-1"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "7fffffffffffffff"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "0x7fffffffffffffff"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "0x8000000000000000"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "-2"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "4r"); - - p = ec_node_parse(node, "10"); - s = ec_strvec_val(ec_parse_strvec(p), 0); - testres |= EC_TEST_CHECK(s != NULL && - ec_node_int_getval(node, s, &i64) == 0 && - i64 == 16, "bad integer value"); - ec_parse_free(p); - ec_node_free(node); - - node = ec_node_int(EC_NO_ID, LLONG_MIN, 0, 10); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "0"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "-1"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "-9223372036854775808"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "0x0"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "1"); - ec_node_free(node); - - /* test completion */ - node = ec_node_int(EC_NO_ID, 0, 10, 0); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "x", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "1", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_int_test = { - .name = "node_int", - .test = ec_node_int_testcase, -}; - -EC_TEST_REGISTER(ec_node_int_test); diff --git a/lib/ecoli_node_int.h b/lib/ecoli_node_int.h deleted file mode 100644 index b64c60c..0000000 --- a/lib/ecoli_node_int.h +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_INT_ -#define ECOLI_NODE_INT_ - -#include - -#include - -/* ec_node("int", ...) can be used too - * default is no limit, base 10 */ - -struct ec_node *ec_node_int(const char *id, int64_t min, - int64_t max, unsigned int base); - -int ec_node_int_getval(const struct ec_node *node, const char *str, - int64_t *result); - - - -struct ec_node *ec_node_uint(const char *id, uint64_t min, - uint64_t max, unsigned int base); - -int ec_node_uint_getval(const struct ec_node *node, const char *str, - uint64_t *result); - - -#endif diff --git a/lib/ecoli_node_many.c b/lib/ecoli_node_many.c deleted file mode 100644 index 1c91f85..0000000 --- a/lib/ecoli_node_many.c +++ /dev/null @@ -1,300 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_many); - -struct ec_node_many { - struct ec_node gen; - unsigned int min; - unsigned int max; - struct ec_node *child; -}; - -static int ec_node_many_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_many *node = (struct ec_node_many *)gen_node; - struct ec_parse *child_parse; - struct ec_strvec *childvec = NULL; - size_t off = 0, count; - int ret; - - for (count = 0; node->max == 0 || count < node->max; count++) { - childvec = ec_strvec_ndup(strvec, off, - ec_strvec_len(strvec) - off); - if (childvec == NULL) - goto fail; - - ret = ec_node_parse_child(node->child, state, childvec); - if (ret < 0) - goto fail; - - ec_strvec_free(childvec); - childvec = NULL; - - if (ret == EC_PARSE_NOMATCH) - break; - - /* it matches an empty strvec, no need to continue */ - if (ret == 0) { - child_parse = ec_parse_get_last_child(state); - ec_parse_unlink_child(state, child_parse); - ec_parse_free(child_parse); - break; - } - - off += ret; - } - - if (count < node->min) { - ec_parse_free_children(state); - return EC_PARSE_NOMATCH; - } - - return off; - -fail: - ec_strvec_free(childvec); - return -1; -} - -static int -__ec_node_many_complete(struct ec_node_many *node, unsigned int max, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_parse *parse = ec_comp_get_state(comp); - struct ec_strvec *childvec = NULL; - unsigned int i; - int ret; - - /* first, try to complete with the child node */ - ret = ec_node_complete_child(node->child, comp, strvec); - if (ret < 0) - goto fail; - - /* we're done, we reached the max number of nodes */ - if (max == 1) - return 0; - - /* if there is a maximum, decrease it before recursion */ - if (max != 0) - max--; - - /* then, if the node matches the beginning of the strvec, try to - * complete the rest */ - for (i = 0; i < ec_strvec_len(strvec); i++) { - childvec = ec_strvec_ndup(strvec, 0, i); - if (childvec == NULL) - goto fail; - - ret = ec_node_parse_child(node->child, parse, childvec); - if (ret < 0) - goto fail; - - ec_strvec_free(childvec); - childvec = NULL; - - if ((unsigned int)ret != i) { - if (ret != EC_PARSE_NOMATCH) - ec_parse_del_last_child(parse); - continue; - } - - childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i); - if (childvec == NULL) { - ec_parse_del_last_child(parse); - goto fail; - } - - ret = __ec_node_many_complete(node, max, comp, childvec); - ec_parse_del_last_child(parse); - ec_strvec_free(childvec); - childvec = NULL; - - if (ret < 0) - goto fail; - } - - return 0; - -fail: - ec_strvec_free(childvec); - return -1; -} - -static int -ec_node_many_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_many *node = (struct ec_node_many *)gen_node; - - return __ec_node_many_complete(node, node->max, comp, - strvec); -} - -static void ec_node_many_free_priv(struct ec_node *gen_node) -{ - struct ec_node_many *node = (struct ec_node_many *)gen_node; - - ec_node_free(node->child); -} - -static size_t -ec_node_many_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_many *node = (struct ec_node_many *)gen_node; - - if (node->child) - return 1; - return 0; -} - -static int -ec_node_many_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_many *node = (struct ec_node_many *)gen_node; - - if (i >= 1) - return -1; - - *child = node->child; - *refs = 1; - return 0; -} - -static struct ec_node_type ec_node_many_type = { - .name = "many", - .parse = ec_node_many_parse, - .complete = ec_node_many_complete, - .size = sizeof(struct ec_node_many), - .free_priv = ec_node_many_free_priv, - .get_children_count = ec_node_many_get_children_count, - .get_child = ec_node_many_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_many_type); - -struct ec_node *ec_node_many(const char *id, struct ec_node *child, - unsigned int min, unsigned int max) -{ - struct ec_node_many *node = NULL; - - if (child == NULL) - return NULL; - - node = (struct ec_node_many *)ec_node_from_type(&ec_node_many_type, id); - if (node == NULL) { - ec_node_free(child); - return NULL; - } - - node->child = child; - node->min = min; - node->max = max; - - return &node->gen; -} - -/* LCOV_EXCL_START */ -static int ec_node_many_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 0, 0); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 0); - testres |= EC_TEST_CHECK_PARSE(node, 0, "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 0); - ec_node_free(node); - - node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 0); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, -1, "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1); - ec_node_free(node); - - node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 2); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, -1, "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1); - ec_node_free(node); - - /* test completion */ - node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 2, 4); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "f", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "foo", "", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "foo", "foo", "", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "foo", "foo", "foo", "", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_many_test = { - .name = "node_many", - .test = ec_node_many_testcase, -}; - -EC_TEST_REGISTER(ec_node_many_test); diff --git a/lib/ecoli_node_many.h b/lib/ecoli_node_many.h deleted file mode 100644 index 14250d1..0000000 --- a/lib/ecoli_node_many.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_MANY_ -#define ECOLI_NODE_MANY_ - -/* - * if min == max == 0, there is no limit - */ -struct ec_node *ec_node_many(const char *id, struct ec_node *child, - unsigned int min, unsigned int max); - -#endif diff --git a/lib/ecoli_node_none.c b/lib/ecoli_node_none.c deleted file mode 100644 index dba9ebf..0000000 --- a/lib/ecoli_node_none.c +++ /dev/null @@ -1,96 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_none); - -struct ec_node_none { - struct ec_node gen; -}; - -static int ec_node_none_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - (void)gen_node; - (void)state; - (void)strvec; - - return EC_PARSE_NOMATCH; -} - -static int -ec_node_none_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - (void)gen_node; - (void)comp; - (void)strvec; - - return 0; -} - -static struct ec_node_type ec_node_none_type = { - .name = "none", - .parse = ec_node_none_parse, - .complete = ec_node_none_complete, - .size = sizeof(struct ec_node_none), -}; - -EC_NODE_TYPE_REGISTER(ec_node_none_type); - -/* LCOV_EXCL_START */ -static int ec_node_none_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node("none", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1); - ec_node_free(node); - - /* never completes */ - node = ec_node("none", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_none_test = { - .name = "node_none", - .test = ec_node_none_testcase, -}; - -EC_TEST_REGISTER(ec_node_none_test); diff --git a/lib/ecoli_node_none.h b/lib/ecoli_node_none.h deleted file mode 100644 index 842f211..0000000 --- a/lib/ecoli_node_none.h +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -/** - * This node does not match anything - */ - -#ifndef ECOLI_NODE_ANY_ -#define ECOLI_NODE_ANY_ - -#endif diff --git a/lib/ecoli_node_once.c b/lib/ecoli_node_once.c deleted file mode 100644 index 6309878..0000000 --- a/lib/ecoli_node_once.c +++ /dev/null @@ -1,228 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_once); - -struct ec_node_once { - struct ec_node gen; - struct ec_node *child; -}; - -static unsigned int -count_node(struct ec_parse *parse, const struct ec_node *node) -{ - struct ec_parse *child; - unsigned int count = 0; - - if (parse == NULL) - return 0; - - if (ec_parse_get_node(parse) == node) - count++; - - EC_PARSE_FOREACH_CHILD(child, parse) - count += count_node(child, node); - - return count; -} - -static int -ec_node_once_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_once *node = (struct ec_node_once *)gen_node; - unsigned int count; - - /* count the number of occurences of the node: if already parsed, - * do not match - */ - count = count_node(ec_parse_get_root(state), node->child); - if (count > 0) - return EC_PARSE_NOMATCH; - - return ec_node_parse_child(node->child, state, strvec); -} - -static int -ec_node_once_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_once *node = (struct ec_node_once *)gen_node; - struct ec_parse *parse = ec_comp_get_state(comp); - unsigned int count; - int ret; - - /* count the number of occurences of the node: if already parsed, - * do not match - */ - count = count_node(ec_parse_get_root(parse), node->child); - if (count > 0) - return 0; - - ret = ec_node_complete_child(node->child, comp, strvec); - if (ret < 0) - return ret; - - return 0; -} - -static void ec_node_once_free_priv(struct ec_node *gen_node) -{ - struct ec_node_once *node = (struct ec_node_once *)gen_node; - - ec_node_free(node->child); -} - -static size_t -ec_node_once_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_once *node = (struct ec_node_once *)gen_node; - - if (node->child) - return 1; - return 0; -} - -static int -ec_node_once_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_once *node = (struct ec_node_once *)gen_node; - - if (i >= 1) - return -1; - - *child = node->child; - *refs = 1; - return 0; -} - -static struct ec_node_type ec_node_once_type = { - .name = "once", - .parse = ec_node_once_parse, - .complete = ec_node_once_complete, - .size = sizeof(struct ec_node_once), - .free_priv = ec_node_once_free_priv, - .get_children_count = ec_node_once_get_children_count, - .get_child = ec_node_once_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_once_type); - -int ec_node_once_set(struct ec_node *gen_node, struct ec_node *child) -{ - struct ec_node_once *node = (struct ec_node_once *)gen_node; - - if (gen_node == NULL || child == NULL) { - errno = EINVAL; - goto fail; - } - - if (ec_node_check_type(gen_node, &ec_node_once_type) < 0) - goto fail; - - node->child = child; - - return 0; - -fail: - ec_node_free(child); - return -1; -} - -struct ec_node *ec_node_once(const char *id, struct ec_node *child) -{ - struct ec_node *gen_node = NULL; - - if (child == NULL) - return NULL; - - gen_node = ec_node_from_type(&ec_node_once_type, id); - if (gen_node == NULL) - goto fail; - - ec_node_once_set(gen_node, child); - child = NULL; - - return gen_node; - -fail: - ec_node_free(child); - return NULL; -} - -/* LCOV_EXCL_START */ -static int ec_node_once_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node_many(EC_NO_ID, - EC_NODE_OR(EC_NO_ID, - ec_node_once(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")), - ec_node_str(EC_NO_ID, "bar") - ), 0, 0 - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 0); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 0, "foox"); - - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "foo", "bar", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "f", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "b", EC_NODE_ENDLIST, - "bar", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "", EC_NODE_ENDLIST, - "bar", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "bar", "", EC_NODE_ENDLIST, - "foo", "bar", EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_once_test = { - .name = "node_once", - .test = ec_node_once_testcase, -}; - -EC_TEST_REGISTER(ec_node_once_test); diff --git a/lib/ecoli_node_once.h b/lib/ecoli_node_once.h deleted file mode 100644 index a610a83..0000000 --- a/lib/ecoli_node_once.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_ONCE_ -#define ECOLI_NODE_ONCE_ - -#include - -/* This node behaves like its child, but prevent from parsing it several - * times. - * - * Example: - * many( - * or( - * once(str("foo")), - * str("bar"))) - * - * Matches: [], ["foo", "bar"], ["bar", "bar"], ["foo", "bar", "bar"], ... - * But not: ["foo", "foo"], ["foo", "bar", "foo"], ... - */ - -/* on error, child is *not* freed */ -struct ec_node *ec_node_once(const char *id, struct ec_node *child); - -/* on error, child is *not* freed */ -int ec_node_once_set(struct ec_node *node, struct ec_node *child); - -#endif diff --git a/lib/ecoli_node_option.c b/lib/ecoli_node_option.c deleted file mode 100644 index ab4f352..0000000 --- a/lib/ecoli_node_option.c +++ /dev/null @@ -1,185 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_option); - -struct ec_node_option { - struct ec_node gen; - struct ec_node *child; -}; - -static int -ec_node_option_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_option *node = (struct ec_node_option *)gen_node; - int ret; - - ret = ec_node_parse_child(node->child, state, strvec); - if (ret < 0) - return ret; - - if (ret == EC_PARSE_NOMATCH) - return 0; - - return ret; -} - -static int -ec_node_option_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_option *node = (struct ec_node_option *)gen_node; - - return ec_node_complete_child(node->child, comp, strvec); -} - -static void ec_node_option_free_priv(struct ec_node *gen_node) -{ - struct ec_node_option *node = (struct ec_node_option *)gen_node; - - ec_node_free(node->child); -} - -static size_t -ec_node_option_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_option *node = (struct ec_node_option *)gen_node; - - if (node->child) - return 1; - return 0; -} - -static int -ec_node_option_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_option *node = (struct ec_node_option *)gen_node; - - if (i >= 1) - return -1; - - *child = node->child; - *refs = 1; - return 0; -} - -static struct ec_node_type ec_node_option_type = { - .name = "option", - .parse = ec_node_option_parse, - .complete = ec_node_option_complete, - .size = sizeof(struct ec_node_option), - .free_priv = ec_node_option_free_priv, - .get_children_count = ec_node_option_get_children_count, - .get_child = ec_node_option_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_option_type); - -int ec_node_option_set(struct ec_node *gen_node, struct ec_node *child) -{ - struct ec_node_option *node = (struct ec_node_option *)gen_node; - - if (gen_node == NULL || child == NULL) { - errno = EINVAL; - goto fail; - } - - if (ec_node_check_type(gen_node, &ec_node_option_type) < 0) - goto fail; - - node->child = child; - - return 0; - -fail: - ec_node_free(child); - return -1; -} - -struct ec_node *ec_node_option(const char *id, struct ec_node *child) -{ - struct ec_node *gen_node = NULL; - - if (child == NULL) - goto fail; - - gen_node = ec_node_from_type(&ec_node_option_type, id); - if (gen_node == NULL) - goto fail; - - ec_node_option_set(gen_node, child); - child = NULL; - - return gen_node; - -fail: - ec_node_free(child); - return NULL; -} - -/* LCOV_EXCL_START */ -static int ec_node_option_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 0, "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 0); - ec_node_free(node); - - /* test completion */ - node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "f", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "b", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_option_test = { - .name = "node_option", - .test = ec_node_option_testcase, -}; - -EC_TEST_REGISTER(ec_node_option_test); diff --git a/lib/ecoli_node_option.h b/lib/ecoli_node_option.h deleted file mode 100644 index 9d67480..0000000 --- a/lib/ecoli_node_option.h +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_OPTION_ -#define ECOLI_NODE_OPTION_ - -#include - -struct ec_node *ec_node_option(const char *id, struct ec_node *node); -int ec_node_option_set(struct ec_node *gen_node, struct ec_node *child); - -#endif diff --git a/lib/ecoli_node_or.c b/lib/ecoli_node_or.c deleted file mode 100644 index 2bf8fc7..0000000 --- a/lib/ecoli_node_or.c +++ /dev/null @@ -1,347 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_or); - -struct ec_node_or { - struct ec_node gen; - struct ec_node **table; - size_t len; -}; - -static int -ec_node_or_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_or *node = (struct ec_node_or *)gen_node; - unsigned int i; - int ret; - - for (i = 0; i < node->len; i++) { - ret = ec_node_parse_child(node->table[i], state, strvec); - if (ret == EC_PARSE_NOMATCH) - continue; - return ret; - } - - return EC_PARSE_NOMATCH; -} - -static int -ec_node_or_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_or *node = (struct ec_node_or *)gen_node; - int ret; - size_t n; - - for (n = 0; n < node->len; n++) { - ret = ec_node_complete_child(node->table[n], - comp, strvec); - if (ret < 0) - return ret; - } - - return 0; -} - -static void ec_node_or_free_priv(struct ec_node *gen_node) -{ - struct ec_node_or *node = (struct ec_node_or *)gen_node; - size_t i; - - for (i = 0; i < node->len; i++) - ec_node_free(node->table[i]); - ec_free(node->table); - node->table = NULL; - node->len = 0; -} - -static const struct ec_config_schema ec_node_or_subschema[] = { - { - .desc = "A child node which is part of the choice.", - .type = EC_CONFIG_TYPE_NODE, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static const struct ec_config_schema ec_node_or_schema[] = { - { - .key = "children", - .desc = "The list of children nodes defining the choice " - "elements.", - .type = EC_CONFIG_TYPE_LIST, - .subschema = ec_node_or_subschema, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_or_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_or *node = (struct ec_node_or *)gen_node; - struct ec_node **table = NULL; - size_t i, len = 0; - - table = ec_node_config_node_list_to_table( - ec_config_dict_get(config, "children"), &len); - if (table == NULL) - goto fail; - - for (i = 0; i < node->len; i++) - ec_node_free(node->table[i]); - ec_free(node->table); - node->table = table; - node->len = len; - - return 0; - -fail: - for (i = 0; i < len; i++) - ec_node_free(table[i]); - ec_free(table); - return -1; -} - -static size_t -ec_node_or_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_or *node = (struct ec_node_or *)gen_node; - return node->len; -} - -static int -ec_node_or_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_or *node = (struct ec_node_or *)gen_node; - - if (i >= node->len) - return -1; - - *child = node->table[i]; - /* each child node is referenced twice: once in the config and - * once in the node->table[] */ - *refs = 2; - return 0; -} - -static struct ec_node_type ec_node_or_type = { - .name = "or", - .schema = ec_node_or_schema, - .set_config = ec_node_or_set_config, - .parse = ec_node_or_parse, - .complete = ec_node_or_complete, - .size = sizeof(struct ec_node_or), - .free_priv = ec_node_or_free_priv, - .get_children_count = ec_node_or_get_children_count, - .get_child = ec_node_or_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_or_type); - -int ec_node_or_add(struct ec_node *gen_node, struct ec_node *child) -{ - struct ec_node_or *node = (struct ec_node_or *)gen_node; - const struct ec_config *cur_config = NULL; - struct ec_config *config = NULL, *children; - int ret; - - assert(node != NULL); - - /* XXX factorize this code in a helper */ - - if (ec_node_check_type(gen_node, &ec_node_or_type) < 0) - goto fail; - - cur_config = ec_node_get_config(gen_node); - if (cur_config == NULL) - config = ec_config_dict(); - else - config = ec_config_dup(cur_config); - if (config == NULL) - goto fail; - - children = ec_config_dict_get(config, "children"); - if (children == NULL) { - children = ec_config_list(); - if (children == NULL) - goto fail; - - if (ec_config_dict_set(config, "children", children) < 0) - goto fail; /* children list is freed on error */ - } - - if (ec_config_list_add(children, ec_config_node(child)) < 0) { - child = NULL; - goto fail; - } - - ret = ec_node_set_config(gen_node, config); - config = NULL; /* freed */ - if (ret < 0) - goto fail; - - return 0; - -fail: - ec_config_free(config); - ec_node_free(child); - return -1; -} - -struct ec_node *__ec_node_or(const char *id, ...) -{ - struct ec_config *config = NULL, *children = NULL; - struct ec_node *gen_node = NULL; - struct ec_node *child; - va_list ap; - int ret; - - va_start(ap, id); - child = va_arg(ap, struct ec_node *); - - gen_node = ec_node_from_type(&ec_node_or_type, id); - if (gen_node == NULL) - goto fail_free_children; - - config = ec_config_dict(); - if (config == NULL) - goto fail_free_children; - - children = ec_config_list(); - if (children == NULL) - goto fail_free_children; - - for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) { - if (child == NULL) - goto fail_free_children; - - if (ec_config_list_add(children, ec_config_node(child)) < 0) { - child = NULL; - goto fail_free_children; - } - } - - if (ec_config_dict_set(config, "children", children) < 0) { - children = NULL; /* freed */ - goto fail; - } - children = NULL; - - ret = ec_node_set_config(gen_node, config); - config = NULL; /* freed */ - if (ret < 0) - goto fail; - - va_end(ap); - - return gen_node; - -fail_free_children: - for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) - ec_node_free(child); -fail: - ec_node_free(gen_node); /* will also free added children */ - ec_config_free(children); - ec_config_free(config); - va_end(ap); - - return NULL; -} - -/* LCOV_EXCL_START */ -static int ec_node_or_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = EC_NODE_OR(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_str(EC_NO_ID, "bar") - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1, " "); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foox"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "toto"); - testres |= EC_TEST_CHECK_PARSE(node, -1, ""); - ec_node_free(node); - - /* test completion */ - node = EC_NODE_OR(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_str(EC_NO_ID, "bar"), - ec_node_str(EC_NO_ID, "bar2"), - ec_node_str(EC_NO_ID, "toto"), - ec_node_str(EC_NO_ID, "titi") - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "f", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "b", EC_NODE_ENDLIST, - "bar", "bar2", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "bar", EC_NODE_ENDLIST, - "bar", "bar2", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "t", EC_NODE_ENDLIST, - "toto", "titi", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "to", EC_NODE_ENDLIST, - "toto", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "x", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_or_test = { - .name = "node_or", - .test = ec_node_or_testcase, -}; - -EC_TEST_REGISTER(ec_node_or_test); diff --git a/lib/ecoli_node_or.h b/lib/ecoli_node_or.h deleted file mode 100644 index db115b2..0000000 --- a/lib/ecoli_node_or.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_OR_ -#define ECOLI_NODE_OR_ - -#include - -#define EC_NODE_OR(args...) __ec_node_or(args, EC_NODE_ENDLIST) - -/* list must be terminated with EC_NODE_ENDLIST */ -/* all nodes given in the list will be freed when freeing this one */ -/* avoid using this function directly, prefer the macro EC_NODE_OR() or - * ec_node_or() + ec_node_or_add() */ -struct ec_node *__ec_node_or(const char *id, ...); - -struct ec_node *ec_node_or(const char *id); - -/* child is consumed */ -int ec_node_or_add(struct ec_node *node, struct ec_node *child); - - -#endif diff --git a/lib/ecoli_node_re.c b/lib/ecoli_node_re.c deleted file mode 100644 index 6ac5182..0000000 --- a/lib/ecoli_node_re.c +++ /dev/null @@ -1,153 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_re); - -struct ec_node_re { - struct ec_node gen; - char *re_str; - regex_t re; -}; - -static int -ec_node_re_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_re *node = (struct ec_node_re *)gen_node; - const char *str; - regmatch_t pos; - - (void)state; - - if (ec_strvec_len(strvec) == 0) - return EC_PARSE_NOMATCH; - - str = ec_strvec_val(strvec, 0); - if (regexec(&node->re, str, 1, &pos, 0) != 0) - return EC_PARSE_NOMATCH; - if (pos.rm_so != 0 || pos.rm_eo != (int)strlen(str)) - return EC_PARSE_NOMATCH; - - return 1; -} - -static void ec_node_re_free_priv(struct ec_node *gen_node) -{ - struct ec_node_re *node = (struct ec_node_re *)gen_node; - - if (node->re_str != NULL) { - ec_free(node->re_str); - regfree(&node->re); - } -} - -static struct ec_node_type ec_node_re_type = { - .name = "re", - .parse = ec_node_re_parse, - .complete = ec_node_complete_unknown, - .size = sizeof(struct ec_node_re), - .free_priv = ec_node_re_free_priv, -}; - -EC_NODE_TYPE_REGISTER(ec_node_re_type); - -int ec_node_re_set_regexp(struct ec_node *gen_node, const char *str) -{ - struct ec_node_re *node = (struct ec_node_re *)gen_node; - char *str_copy = NULL; - regex_t re; - int ret; - - EC_CHECK_ARG(str != NULL, -1, EINVAL); - - str_copy = ec_strdup(str); - if (str_copy == NULL) - goto fail; - - ret = regcomp(&re, str_copy, REG_EXTENDED); - if (ret != 0) { - if (ret == REG_ESPACE) - errno = ENOMEM; - else - errno = EINVAL; - goto fail; - } - - if (node->re_str != NULL) { - ec_free(node->re_str); - regfree(&node->re); - } - node->re_str = str_copy; - node->re = re; - - return 0; - -fail: - ec_free(str_copy); - return -1; -} - -struct ec_node *ec_node_re(const char *id, const char *re_str) -{ - struct ec_node *gen_node = NULL; - - gen_node = ec_node_from_type(&ec_node_re_type, id); - if (gen_node == NULL) - goto fail; - - if (ec_node_re_set_regexp(gen_node, re_str) < 0) - goto fail; - - return gen_node; - -fail: - ec_node_free(gen_node); - return NULL; -} - -/* LCOV_EXCL_START */ -static int ec_node_re_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node_re(EC_NO_ID, "fo+|bar"); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar"); - testres |= EC_TEST_CHECK_PARSE(node, -1, " foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, ""); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_re_test = { - .name = "node_re", - .test = ec_node_re_testcase, -}; - -EC_TEST_REGISTER(ec_node_re_test); diff --git a/lib/ecoli_node_re.h b/lib/ecoli_node_re.h deleted file mode 100644 index bc3a317..0000000 --- a/lib/ecoli_node_re.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_RE_ -#define ECOLI_NODE_RE_ - -#include - -struct ec_node *ec_node_re(const char *id, const char *str); - -/* re is duplicated */ -int ec_node_re_set_regexp(struct ec_node *node, const char *re); - -#endif diff --git a/lib/ecoli_node_re_lex.c b/lib/ecoli_node_re_lex.c deleted file mode 100644 index f5a6c37..0000000 --- a/lib/ecoli_node_re_lex.c +++ /dev/null @@ -1,308 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_re_lex); - -struct regexp_pattern { - char *pattern; - regex_t r; - bool keep; -}; - -struct ec_node_re_lex { - struct ec_node gen; - struct ec_node *child; - struct regexp_pattern *table; - size_t len; -}; - -static struct ec_strvec * -tokenize(struct regexp_pattern *table, size_t table_len, const char *str) -{ - struct ec_strvec *strvec = NULL; - char *dup = NULL; - char c; - size_t len, off = 0; - size_t i; - int ret; - regmatch_t pos; - - dup = ec_strdup(str); - if (dup == NULL) - goto fail; - - strvec = ec_strvec(); - if (strvec == NULL) - goto fail; - - len = strlen(dup); - while (off < len) { - for (i = 0; i < table_len; i++) { - ret = regexec(&table[i].r, &dup[off], 1, &pos, 0); - if (ret != 0) - continue; - if (pos.rm_so != 0 || pos.rm_eo == 0) { - ret = -1; - continue; - } - - if (table[i].keep == 0) - break; - - c = dup[pos.rm_eo + off]; - dup[pos.rm_eo + off] = '\0'; - EC_LOG(EC_LOG_DEBUG, "re_lex match <%s>\n", &dup[off]); - if (ec_strvec_add(strvec, &dup[off]) < 0) - goto fail; - - dup[pos.rm_eo + off] = c; - break; - } - - if (ret != 0) - goto fail; - - off += pos.rm_eo; - } - - ec_free(dup); - return strvec; - -fail: - ec_free(dup); - ec_strvec_free(strvec); - return NULL; -} - -static int -ec_node_re_lex_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; - struct ec_strvec *new_vec = NULL; - struct ec_parse *child_parse; - const char *str; - int ret; - - if (ec_strvec_len(strvec) == 0) { - new_vec = ec_strvec(); - } else { - str = ec_strvec_val(strvec, 0); - new_vec = tokenize(node->table, node->len, str); - } - if (new_vec == NULL) - goto fail; - - ret = ec_node_parse_child(node->child, state, new_vec); - if (ret < 0) - goto fail; - - if ((unsigned)ret == ec_strvec_len(new_vec)) { - ret = 1; - } else if (ret != EC_PARSE_NOMATCH) { - child_parse = ec_parse_get_last_child(state); - ec_parse_unlink_child(state, child_parse); - ec_parse_free(child_parse); - ret = EC_PARSE_NOMATCH; - } - - ec_strvec_free(new_vec); - new_vec = NULL; - - return ret; - - fail: - ec_strvec_free(new_vec); - return -1; -} - -static void ec_node_re_lex_free_priv(struct ec_node *gen_node) -{ - struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; - unsigned int i; - - ec_node_free(node->child); - for (i = 0; i < node->len; i++) { - ec_free(node->table[i].pattern); - regfree(&node->table[i].r); - } - - ec_free(node->table); -} - -static size_t -ec_node_re_lex_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; - - if (node->child) - return 1; - return 0; -} - -static int -ec_node_re_lex_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; - - if (i >= 1) - return -1; - - *child = node->child; - *refs = 1; - return 0; -} - -static struct ec_node_type ec_node_re_lex_type = { - .name = "re_lex", - .parse = ec_node_re_lex_parse, - .complete = ec_node_complete_unknown, - .size = sizeof(struct ec_node_re_lex), - .free_priv = ec_node_re_lex_free_priv, - .get_children_count = ec_node_re_lex_get_children_count, - .get_child = ec_node_re_lex_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_re_lex_type); - -int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep) -{ - struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; - struct regexp_pattern *table; - int ret; - char *pat_dup = NULL; - - pat_dup = ec_strdup(pattern); - if (pat_dup == NULL) - goto fail; - - table = ec_realloc(node->table, sizeof(*table) * (node->len + 1)); - if (table == NULL) - goto fail; - - ret = regcomp(&table[node->len].r, pattern, REG_EXTENDED); - if (ret != 0) { - EC_LOG(EC_LOG_ERR, - "Regular expression <%s> compilation failed: %d\n", - pattern, ret); - if (ret == REG_ESPACE) - errno = ENOMEM; - else - errno = EINVAL; - - goto fail; - } - - table[node->len].pattern = pat_dup; - table[node->len].keep = keep; - node->len++; - node->table = table; - - return 0; - -fail: - ec_free(pat_dup); - return -1; -} - -struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child) -{ - struct ec_node_re_lex *node = NULL; - - if (child == NULL) - return NULL; - - node = (struct ec_node_re_lex *)ec_node_from_type(&ec_node_re_lex_type, id); - if (node == NULL) { - ec_node_free(child); - return NULL; - } - - node->child = child; - - return &node->gen; -} - -/* LCOV_EXCL_START */ -static int ec_node_re_lex_testcase(void) -{ - struct ec_node *node; - int ret, testres = 0; - - node = ec_node_re_lex(EC_NO_ID, - ec_node_many(EC_NO_ID, - EC_NODE_OR(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_str(EC_NO_ID, "bar"), - ec_node_int(EC_NO_ID, 0, 1000, 0) - ), 0, 0 - ) - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - - ret = ec_node_re_lex_add(node, "[a-zA-Z]+", 1); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "[0-9]+", 1); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "=", 1); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "-", 1); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "\\+", 1); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "[ ]+", 0); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - if (ret != 0) { - EC_LOG(EC_LOG_ERR, "cannot add regexp to node\n"); - ec_node_free(node); - return -1; - } - - testres |= EC_TEST_CHECK_PARSE(node, 1, " foo bar 324 bar234"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar324"); - testres |= EC_TEST_CHECK_PARSE(node, 1, ""); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar"); - - /* no completion */ - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_re_lex_test = { - .name = "node_re_lex", - .test = ec_node_re_lex_testcase, -}; - -EC_TEST_REGISTER(ec_node_re_lex_test); diff --git a/lib/ecoli_node_re_lex.h b/lib/ecoli_node_re_lex.h deleted file mode 100644 index 632627c..0000000 --- a/lib/ecoli_node_re_lex.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_RE_LEX_ -#define ECOLI_NODE_RE_LEX_ - -#include - -struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child); - -int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep); - -#endif diff --git a/lib/ecoli_node_seq.c b/lib/ecoli_node_seq.c deleted file mode 100644 index ff0c5de..0000000 --- a/lib/ecoli_node_seq.c +++ /dev/null @@ -1,442 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_seq); - -struct ec_node_seq { - struct ec_node gen; - struct ec_node **table; - size_t len; -}; - -static int -ec_node_seq_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_seq *node = (struct ec_node_seq *)gen_node; - struct ec_strvec *childvec = NULL; - size_t len = 0; - unsigned int i; - int ret; - - for (i = 0; i < node->len; i++) { - childvec = ec_strvec_ndup(strvec, len, - ec_strvec_len(strvec) - len); - if (childvec == NULL) - goto fail; - - ret = ec_node_parse_child(node->table[i], state, childvec); - if (ret < 0) - goto fail; - - ec_strvec_free(childvec); - childvec = NULL; - - if (ret == EC_PARSE_NOMATCH) { - ec_parse_free_children(state); - return EC_PARSE_NOMATCH; - } - - len += ret; - } - - return len; - -fail: - ec_strvec_free(childvec); - return -1; -} - -static int -__ec_node_seq_complete(struct ec_node **table, size_t table_len, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_parse *parse = ec_comp_get_state(comp); - struct ec_strvec *childvec = NULL; - unsigned int i; - int ret; - - if (table_len == 0) - return 0; - - /* - * Example of completion for a sequence node = [n1,n2] and an - * input = [a,b,c,d]: - * - * result = complete(n1, [a,b,c,d]) + - * complete(n2, [b,c,d]) if n1 matches [a] + - * complete(n2, [c,d]) if n1 matches [a,b] + - * complete(n2, [d]) if n1 matches [a,b,c] + - * complete(n2, []) if n1 matches [a,b,c,d] - */ - - /* first, try to complete with the first node of the table */ - ret = ec_node_complete_child(table[0], comp, strvec); - if (ret < 0) - goto fail; - - /* then, if the first node of the table matches the beginning of the - * strvec, try to complete the rest */ - for (i = 0; i < ec_strvec_len(strvec); i++) { - childvec = ec_strvec_ndup(strvec, 0, i); - if (childvec == NULL) - goto fail; - - ret = ec_node_parse_child(table[0], parse, childvec); - if (ret < 0) - goto fail; - - ec_strvec_free(childvec); - childvec = NULL; - - if ((unsigned int)ret != i) { - if (ret != EC_PARSE_NOMATCH) - ec_parse_del_last_child(parse); - continue; - } - - childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i); - if (childvec == NULL) { - ec_parse_del_last_child(parse); - goto fail; - } - - ret = __ec_node_seq_complete(&table[1], - table_len - 1, - comp, childvec); - ec_parse_del_last_child(parse); - ec_strvec_free(childvec); - childvec = NULL; - - if (ret < 0) - goto fail; - } - - return 0; - -fail: - ec_strvec_free(childvec); - return -1; -} - -static int -ec_node_seq_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_seq *node = (struct ec_node_seq *)gen_node; - - return __ec_node_seq_complete(node->table, node->len, comp, - strvec); -} - -static void ec_node_seq_free_priv(struct ec_node *gen_node) -{ - struct ec_node_seq *node = (struct ec_node_seq *)gen_node; - size_t i; - - for (i = 0; i < node->len; i++) - ec_node_free(node->table[i]); - ec_free(node->table); - node->table = NULL; - node->len = 0; -} - -static const struct ec_config_schema ec_node_seq_subschema[] = { - { - .desc = "A child node which is part of the sequence.", - .type = EC_CONFIG_TYPE_NODE, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static const struct ec_config_schema ec_node_seq_schema[] = { - { - .key = "children", - .desc = "The list of children nodes, to be parsed in sequence.", - .type = EC_CONFIG_TYPE_LIST, - .subschema = ec_node_seq_subschema, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_seq_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_seq *node = (struct ec_node_seq *)gen_node; - struct ec_node **table = NULL; - size_t i, len = 0; - - table = ec_node_config_node_list_to_table( - ec_config_dict_get(config, "children"), &len); - if (table == NULL) - goto fail; - - for (i = 0; i < node->len; i++) - ec_node_free(node->table[i]); - ec_free(node->table); - node->table = table; - node->len = len; - - return 0; - -fail: - for (i = 0; i < len; i++) - ec_node_free(table[i]); - ec_free(table); - return -1; -} - -static size_t -ec_node_seq_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_seq *node = (struct ec_node_seq *)gen_node; - return node->len; -} - -static int -ec_node_seq_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_seq *node = (struct ec_node_seq *)gen_node; - - if (i >= node->len) - return -1; - - *child = node->table[i]; - /* each child node is referenced twice: once in the config and - * once in the node->table[] */ - *refs = 2; - return 0; -} - -static struct ec_node_type ec_node_seq_type = { - .name = "seq", - .schema = ec_node_seq_schema, - .set_config = ec_node_seq_set_config, - .parse = ec_node_seq_parse, - .complete = ec_node_seq_complete, - .size = sizeof(struct ec_node_seq), - .free_priv = ec_node_seq_free_priv, - .get_children_count = ec_node_seq_get_children_count, - .get_child = ec_node_seq_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_seq_type); - -int ec_node_seq_add(struct ec_node *gen_node, struct ec_node *child) -{ - struct ec_node_seq *node = (struct ec_node_seq *)gen_node; - const struct ec_config *cur_config = NULL; - struct ec_config *config = NULL, *children; - int ret; - - assert(node != NULL); - - /* XXX factorize this code in a helper */ - - if (ec_node_check_type(gen_node, &ec_node_seq_type) < 0) - goto fail; - - cur_config = ec_node_get_config(gen_node); - if (cur_config == NULL) - config = ec_config_dict(); - else - config = ec_config_dup(cur_config); - if (config == NULL) - goto fail; - - children = ec_config_dict_get(config, "children"); - if (children == NULL) { - children = ec_config_list(); - if (children == NULL) - goto fail; - - if (ec_config_dict_set(config, "children", children) < 0) - goto fail; /* children list is freed on error */ - } - - if (ec_config_list_add(children, ec_config_node(child)) < 0) { - child = NULL; - goto fail; - } - - ret = ec_node_set_config(gen_node, config); - config = NULL; /* freed */ - if (ret < 0) - goto fail; - - return 0; - -fail: - ec_config_free(config); - ec_node_free(child); - return -1; -} - -struct ec_node *__ec_node_seq(const char *id, ...) -{ - struct ec_config *config = NULL, *children = NULL; - struct ec_node *gen_node = NULL; - struct ec_node *child; - va_list ap; - int ret; - - va_start(ap, id); - child = va_arg(ap, struct ec_node *); - - gen_node = ec_node_from_type(&ec_node_seq_type, id); - if (gen_node == NULL) - goto fail_free_children; - - config = ec_config_dict(); - if (config == NULL) - goto fail_free_children; - - children = ec_config_list(); - if (children == NULL) - goto fail_free_children; - - for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) { - if (child == NULL) - goto fail_free_children; - - if (ec_config_list_add(children, ec_config_node(child)) < 0) { - child = NULL; - goto fail_free_children; - } - } - - if (ec_config_dict_set(config, "children", children) < 0) { - children = NULL; /* freed */ - goto fail; - } - children = NULL; - - ret = ec_node_set_config(gen_node, config); - config = NULL; /* freed */ - if (ret < 0) - goto fail; - - va_end(ap); - - return gen_node; - -fail_free_children: - for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) - ec_node_free(child); -fail: - ec_node_free(gen_node); /* will also free added children */ - ec_config_free(children); - ec_config_free(config); - va_end(ap); - - return NULL; -} - -/* LCOV_EXCL_START */ -static int ec_node_seq_testcase(void) -{ - struct ec_node *node = NULL; - int testres = 0; - - node = EC_NODE_SEQ(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_str(EC_NO_ID, "bar") - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "toto"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foox", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "barx"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "bar", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "", "foo"); - - testres |= (ec_node_seq_add(node, ec_node_str(EC_NO_ID, "grr")) < 0); - testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "grr"); - - ec_node_free(node); - - /* test completion */ - node = EC_NODE_SEQ(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "toto")), - ec_node_str(EC_NO_ID, "bar") - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "f", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "", EC_NODE_ENDLIST, - "bar", "toto", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "t", EC_NODE_ENDLIST, - "toto", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "b", EC_NODE_ENDLIST, - "bar", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", "bar", EC_NODE_ENDLIST, - "bar", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "x", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foobarx", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_seq_test = { - .name = "node_seq", - .test = ec_node_seq_testcase, -}; - -EC_TEST_REGISTER(ec_node_seq_test); diff --git a/lib/ecoli_node_seq.h b/lib/ecoli_node_seq.h deleted file mode 100644 index 21c96b1..0000000 --- a/lib/ecoli_node_seq.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_SEQ_ -#define ECOLI_NODE_SEQ_ - -#include - -#define EC_NODE_SEQ(args...) __ec_node_seq(args, EC_NODE_ENDLIST) - -/* list must be terminated with EC_NODE_ENDLIST */ -/* all nodes given in the list will be freed when freeing this one */ -/* avoid using this function directly, prefer the macro EC_NODE_SEQ() or - * ec_node_seq() + ec_node_seq_add() */ -struct ec_node *__ec_node_seq(const char *id, ...); - -struct ec_node *ec_node_seq(const char *id); - -/* child is consumed */ -int ec_node_seq_add(struct ec_node *node, struct ec_node *child); - -#endif diff --git a/lib/ecoli_node_sh_lex.c b/lib/ecoli_node_sh_lex.c deleted file mode 100644 index e27f21b..0000000 --- a/lib/ecoli_node_sh_lex.c +++ /dev/null @@ -1,491 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_sh_lex); - -struct ec_node_sh_lex { - struct ec_node gen; - struct ec_node *child; -}; - -static size_t eat_spaces(const char *str) -{ - size_t i = 0; - - /* skip spaces */ - while (isblank(str[i])) - i++; - - return i; -} - -/* - * Allocate a new string which is a copy of the input string with quotes - * removed. If quotes are not closed properly, set missing_quote to the - * missing quote char. - */ -static char *unquote_str(const char *str, size_t n, int allow_missing_quote, - char *missing_quote) -{ - unsigned s = 1, d = 0; - char quote = str[0]; - char *dst; - int closed = 0; - - dst = ec_malloc(n); - if (dst == NULL) { - errno = ENOMEM; - return NULL; - } - - /* copy string and remove quotes */ - while (s < n && d < n && str[s] != '\0') { - if (str[s] == '\\' && str[s+1] == quote) { - dst[d++] = quote; - s += 2; - continue; - } - if (str[s] == '\\' && str[s+1] == '\\') { - dst[d++] = '\\'; - s += 2; - continue; - } - if (str[s] == quote) { - s++; - closed = 1; - break; - } - dst[d++] = str[s++]; - } - - /* not enough room in dst buffer (should not happen) */ - if (d >= n) { - ec_free(dst); - errno = EMSGSIZE; - return NULL; - } - - /* quote not closed */ - if (closed == 0) { - if (missing_quote != NULL) - *missing_quote = str[0]; - if (allow_missing_quote == 0) { - ec_free(dst); - errno = EBADMSG; - return NULL; - } - } - dst[d++] = '\0'; - - return dst; -} - -static size_t eat_quoted_str(const char *str) -{ - size_t i = 0; - char quote = str[0]; - - while (str[i] != '\0') { - if (str[i] != '\\' && str[i+1] == quote) - return i + 2; - i++; - } - - /* unclosed quote, will be detected later */ - return i; -} - -static size_t eat_str(const char *str) -{ - size_t i = 0; - - /* eat chars until we find a quote, space, or end of string */ - while (!isblank(str[i]) && str[i] != '\0' && - str[i] != '"' && str[i] != '\'') - i++; - - return i; -} - -static struct ec_strvec *tokenize(const char *str, int completion, - int allow_missing_quote, char *missing_quote) -{ - struct ec_strvec *strvec = NULL; - size_t off = 0, len, suboff, sublen; - char *word = NULL, *concat = NULL, *tmp; - int last_is_space = 1; - - strvec = ec_strvec(); - if (strvec == NULL) - goto fail; - - while (str[off] != '\0') { - if (missing_quote != NULL) - *missing_quote = '\0'; - len = eat_spaces(&str[off]); - if (len > 0) - last_is_space = 1; - off += len; - - len = 0; - suboff = off; - while (str[suboff] != '\0') { - if (missing_quote != NULL) - *missing_quote = '\0'; - last_is_space = 0; - if (str[suboff] == '"' || str[suboff] == '\'') { - sublen = eat_quoted_str(&str[suboff]); - word = unquote_str(&str[suboff], sublen, - allow_missing_quote, missing_quote); - } else { - sublen = eat_str(&str[suboff]); - if (sublen == 0) - break; - word = ec_strndup(&str[suboff], sublen); - } - - if (word == NULL) - goto fail; - - len += sublen; - suboff += sublen; - - if (concat == NULL) { - concat = word; - word = NULL; - } else { - tmp = ec_realloc(concat, len + 1); - if (tmp == NULL) - goto fail; - concat = tmp; - strcat(concat, word); - ec_free(word); - word = NULL; - } - } - - if (concat != NULL) { - if (ec_strvec_add(strvec, concat) < 0) - goto fail; - ec_free(concat); - concat = NULL; - } - - off += len; - } - - /* in completion mode, append an empty string in the vector if - * the input string ends with space */ - if (completion && last_is_space) { - if (ec_strvec_add(strvec, "") < 0) - goto fail; - } - - return strvec; - - fail: - ec_free(word); - ec_free(concat); - ec_strvec_free(strvec); - return NULL; -} - -static int -ec_node_sh_lex_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; - struct ec_strvec *new_vec = NULL; - struct ec_parse *child_parse; - const char *str; - int ret; - - if (ec_strvec_len(strvec) == 0) { - new_vec = ec_strvec(); - } else { - str = ec_strvec_val(strvec, 0); - new_vec = tokenize(str, 0, 0, NULL); - } - if (new_vec == NULL && errno == EBADMSG) /* quotes not closed */ - return EC_PARSE_NOMATCH; - if (new_vec == NULL) - goto fail; - - ret = ec_node_parse_child(node->child, state, new_vec); - if (ret < 0) - goto fail; - - if ((unsigned)ret == ec_strvec_len(new_vec)) { - ret = 1; - } else if (ret != EC_PARSE_NOMATCH) { - child_parse = ec_parse_get_last_child(state); - ec_parse_unlink_child(state, child_parse); - ec_parse_free(child_parse); - ret = EC_PARSE_NOMATCH; - } - - ec_strvec_free(new_vec); - new_vec = NULL; - - return ret; - - fail: - ec_strvec_free(new_vec); - return -1; -} - -static int -ec_node_sh_lex_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; - struct ec_comp *tmp_comp = NULL; - struct ec_strvec *new_vec = NULL; - struct ec_comp_iter *iter = NULL; - struct ec_comp_item *item = NULL; - char *new_str = NULL; - const char *str; - char missing_quote = '\0'; - int ret; - - if (ec_strvec_len(strvec) != 1) - return 0; - - str = ec_strvec_val(strvec, 0); - new_vec = tokenize(str, 1, 1, &missing_quote); - if (new_vec == NULL) - goto fail; - - /* we will store the completions in a temporary struct, because - * we want to update them (ex: add missing quotes) */ - tmp_comp = ec_comp(ec_comp_get_state(comp)); - if (tmp_comp == NULL) - goto fail; - - ret = ec_node_complete_child(node->child, tmp_comp, new_vec); - if (ret < 0) - goto fail; - - /* add missing quote for full completions */ - if (missing_quote != '\0') { - iter = ec_comp_iter(tmp_comp, EC_COMP_FULL); - if (iter == NULL) - goto fail; - while ((item = ec_comp_iter_next(iter)) != NULL) { - str = ec_comp_item_get_str(item); - if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str, - missing_quote) < 0) { - new_str = NULL; - goto fail; - } - if (ec_comp_item_set_str(item, new_str) < 0) - goto fail; - ec_free(new_str); - new_str = NULL; - - str = ec_comp_item_get_completion(item); - if (ec_asprintf(&new_str, "%s%c", str, - missing_quote) < 0) { - new_str = NULL; - goto fail; - } - if (ec_comp_item_set_completion(item, new_str) < 0) - goto fail; - ec_free(new_str); - new_str = NULL; - } - } - - ec_comp_iter_free(iter); - ec_strvec_free(new_vec); - - ec_comp_merge(comp, tmp_comp); - - return 0; - - fail: - ec_comp_free(tmp_comp); - ec_comp_iter_free(iter); - ec_strvec_free(new_vec); - ec_free(new_str); - - return -1; -} - -static void ec_node_sh_lex_free_priv(struct ec_node *gen_node) -{ - struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; - - ec_node_free(node->child); -} - -static size_t -ec_node_sh_lex_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; - - if (node->child) - return 1; - return 0; -} - -static int -ec_node_sh_lex_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; - - if (i >= 1) - return -1; - - *refs = 1; - *child = node->child; - return 0; -} - -static struct ec_node_type ec_node_sh_lex_type = { - .name = "sh_lex", - .parse = ec_node_sh_lex_parse, - .complete = ec_node_sh_lex_complete, - .size = sizeof(struct ec_node_sh_lex), - .free_priv = ec_node_sh_lex_free_priv, - .get_children_count = ec_node_sh_lex_get_children_count, - .get_child = ec_node_sh_lex_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type); - -struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child) -{ - struct ec_node_sh_lex *node = NULL; - - if (child == NULL) - return NULL; - - node = (struct ec_node_sh_lex *)ec_node_from_type(&ec_node_sh_lex_type, id); - if (node == NULL) { - ec_node_free(child); - return NULL; - } - - node->child = child; - - return &node->gen; -} - -/* LCOV_EXCL_START */ -static int ec_node_sh_lex_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node_sh_lex(EC_NO_ID, - EC_NODE_SEQ(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_option(EC_NO_ID, - ec_node_str(EC_NO_ID, "toto") - ), - ec_node_str(EC_NO_ID, "bar") - ) - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar"); - testres |= EC_TEST_CHECK_PARSE(node, 1, " foo bar"); - testres |= EC_TEST_CHECK_PARSE(node, 1, " 'foo' \"bar\""); - testres |= EC_TEST_CHECK_PARSE(node, 1, " 'f'oo 'toto' bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1, " foo toto bar'"); - ec_node_free(node); - - /* test completion */ - node = ec_node_sh_lex(EC_NO_ID, - EC_NODE_SEQ(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_option(EC_NO_ID, - ec_node_str(EC_NO_ID, "toto") - ), - ec_node_str(EC_NO_ID, "bar"), - ec_node_str(EC_NO_ID, "titi") - ) - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - " ", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "f", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo ", EC_NODE_ENDLIST, - "bar", "toto", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo t", EC_NODE_ENDLIST, - "toto", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo b", EC_NODE_ENDLIST, - "bar", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo bar", EC_NODE_ENDLIST, - "bar", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo bar ", EC_NODE_ENDLIST, - "titi", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo toto bar ", EC_NODE_ENDLIST, - "titi", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "x", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo barx", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo 'b", EC_NODE_ENDLIST, - "'bar'", EC_NODE_ENDLIST); - - ec_node_free(node); - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_sh_lex_test = { - .name = "node_sh_lex", - .test = ec_node_sh_lex_testcase, -}; - -EC_TEST_REGISTER(ec_node_sh_lex_test); diff --git a/lib/ecoli_node_sh_lex.h b/lib/ecoli_node_sh_lex.h deleted file mode 100644 index d45b998..0000000 --- a/lib/ecoli_node_sh_lex.h +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_SHLEX_ -#define ECOLI_NODE_SHLEX_ - -#include - -struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child); - -#endif diff --git a/lib/ecoli_node_space.c b/lib/ecoli_node_space.c deleted file mode 100644 index 761ed76..0000000 --- a/lib/ecoli_node_space.c +++ /dev/null @@ -1,102 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_space); - -struct ec_node_space { - struct ec_node gen; -}; - -static int -ec_node_space_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - const char *str; - size_t len = 0; - - (void)state; - (void)gen_node; - - if (ec_strvec_len(strvec) == 0) - return EC_PARSE_NOMATCH; - - str = ec_strvec_val(strvec, 0); - while (isspace(str[len])) - len++; - if (len == 0 || len != strlen(str)) - return EC_PARSE_NOMATCH; - - return 1; -} - -static struct ec_node_type ec_node_space_type = { - .name = "space", - .parse = ec_node_space_parse, - .complete = ec_node_complete_unknown, - .size = sizeof(struct ec_node_space), -}; - -EC_NODE_TYPE_REGISTER(ec_node_space_type); - -/* LCOV_EXCL_START */ -static int ec_node_space_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node("space", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, " "); - testres |= EC_TEST_CHECK_PARSE(node, 1, " ", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, ""); - testres |= EC_TEST_CHECK_PARSE(node, -1, " foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foo "); - ec_node_free(node); - - /* test completion */ - node = ec_node("space", EC_NO_ID); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - /* never completes whatever the input */ - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - " ", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_space_test = { - .name = "space", - .test = ec_node_space_testcase, -}; - -EC_TEST_REGISTER(ec_node_space_test); diff --git a/lib/ecoli_node_space.h b/lib/ecoli_node_space.h deleted file mode 100644 index 0dd6202..0000000 --- a/lib/ecoli_node_space.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * This node matches one string in the vector if it is only composed of - * spaces, as interpreted by isspace(). - */ - -#ifndef ECOLI_NODE_SPACE_ -#define ECOLI_NODE_SPACE_ - -/* no API for now, since there is no specific configuration for this node */ - -#endif diff --git a/lib/ecoli_node_str.c b/lib/ecoli_node_str.c deleted file mode 100644 index d53ea39..0000000 --- a/lib/ecoli_node_str.c +++ /dev/null @@ -1,274 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_str); - -struct ec_node_str { - struct ec_node gen; - char *string; - unsigned len; -}; - -static int -ec_node_str_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_str *node = (struct ec_node_str *)gen_node; - const char *str; - - (void)state; - - if (node->string == NULL) { - errno = EINVAL; - return -1; - } - - if (ec_strvec_len(strvec) == 0) - return EC_PARSE_NOMATCH; - - str = ec_strvec_val(strvec, 0); - if (strcmp(str, node->string) != 0) - return EC_PARSE_NOMATCH; - - return 1; -} - -static int -ec_node_str_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_str *node = (struct ec_node_str *)gen_node; - const char *str; - size_t n = 0; - - if (ec_strvec_len(strvec) != 1) - return 0; - - str = ec_strvec_val(strvec, 0); - for (n = 0; n < node->len; n++) { - if (str[n] != node->string[n]) - break; - } - - /* no completion */ - if (str[n] != '\0') - return EC_PARSE_NOMATCH; - - if (ec_comp_add_item(comp, gen_node, NULL, EC_COMP_FULL, - str, node->string) < 0) - return -1; - - return 0; -} - -static const char *ec_node_str_desc(const struct ec_node *gen_node) -{ - struct ec_node_str *node = (struct ec_node_str *)gen_node; - - return node->string; -} - -static void ec_node_str_free_priv(struct ec_node *gen_node) -{ - struct ec_node_str *node = (struct ec_node_str *)gen_node; - - ec_free(node->string); -} - -static const struct ec_config_schema ec_node_str_schema[] = { - { - .key = "string", - .desc = "The string to match.", - .type = EC_CONFIG_TYPE_STRING, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_str_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_str *node = (struct ec_node_str *)gen_node; - const struct ec_config *value = NULL; - char *s = NULL; - - value = ec_config_dict_get(config, "string"); - if (value == NULL) { - errno = EINVAL; - goto fail; - } - - s = ec_strdup(value->string); - if (s == NULL) - goto fail; - - ec_free(node->string); - node->string = s; - node->len = strlen(node->string); - - return 0; - -fail: - ec_free(s); - return -1; -} - -static struct ec_node_type ec_node_str_type = { - .name = "str", - .schema = ec_node_str_schema, - .set_config = ec_node_str_set_config, - .parse = ec_node_str_parse, - .complete = ec_node_str_complete, - .desc = ec_node_str_desc, - .size = sizeof(struct ec_node_str), - .free_priv = ec_node_str_free_priv, -}; - -EC_NODE_TYPE_REGISTER(ec_node_str_type); - -int ec_node_str_set_str(struct ec_node *gen_node, const char *str) -{ - struct ec_config *config = NULL; - int ret; - - if (ec_node_check_type(gen_node, &ec_node_str_type) < 0) - goto fail; - - if (str == NULL) { - errno = EINVAL; - goto fail; - } - - config = ec_config_dict(); - if (config == NULL) - goto fail; - - ret = ec_config_dict_set(config, "string", ec_config_string(str)); - if (ret < 0) - goto fail; - - ret = ec_node_set_config(gen_node, config); - config = NULL; - if (ret < 0) - goto fail; - - return 0; - -fail: - ec_config_free(config); - return -1; -} - -struct ec_node *ec_node_str(const char *id, const char *str) -{ - struct ec_node *gen_node = NULL; - - gen_node = ec_node_from_type(&ec_node_str_type, id); - if (gen_node == NULL) - goto fail; - - if (ec_node_str_set_str(gen_node, str) < 0) - goto fail; - - return gen_node; - -fail: - ec_node_free(gen_node); - return NULL; -} - -/* LCOV_EXCL_START */ -static int ec_node_str_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = ec_node_str(EC_NO_ID, "foo"); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK(!strcmp(ec_node_desc(node), "foo"), - "Invalid node description."); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar"); - testres |= EC_TEST_CHECK_PARSE(node, -1, " foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, ""); - ec_node_free(node); - - node = ec_node_str(EC_NO_ID, "Здравствуйте"); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте", - "John!"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, ""); - ec_node_free(node); - - /* an empty string node always matches */ - node = ec_node_str(EC_NO_ID, ""); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 1, ""); - testres |= EC_TEST_CHECK_PARSE(node, 1, "", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); - ec_node_free(node); - - /* test completion */ - node = ec_node_str(EC_NO_ID, "foo"); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "f", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "foo", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "x", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_str_test = { - .name = "node_str", - .test = ec_node_str_testcase, -}; - -EC_TEST_REGISTER(ec_node_str_test); diff --git a/lib/ecoli_node_str.h b/lib/ecoli_node_str.h deleted file mode 100644 index 8a8634f..0000000 --- a/lib/ecoli_node_str.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_STR_ -#define ECOLI_NODE_STR_ - -#include - -struct ec_node *ec_node_str(const char *id, const char *str); - -/* str is duplicated */ -int ec_node_str_set_str(struct ec_node *node, const char *str); - -#endif diff --git a/lib/ecoli_node_subset.c b/lib/ecoli_node_subset.c deleted file mode 100644 index e3184ef..0000000 --- a/lib/ecoli_node_subset.c +++ /dev/null @@ -1,430 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(node_subset); - -struct ec_node_subset { - struct ec_node gen; - struct ec_node **table; - unsigned int len; -}; - -struct parse_result { - size_t parse_len; /* number of parsed nodes */ - size_t len; /* consumed strings */ -}; - -/* recursively find the longest list of nodes that matches: the state is - * updated accordingly. */ -static int -__ec_node_subset_parse(struct parse_result *out, struct ec_node **table, - size_t table_len, struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node **child_table; - struct ec_strvec *childvec = NULL; - size_t i, j, len = 0; - struct parse_result best_result, result; - struct ec_parse *best_parse = NULL; - int ret; - - if (table_len == 0) - return 0; - - memset(&best_result, 0, sizeof(best_result)); - - child_table = ec_calloc(table_len - 1, sizeof(*child_table)); - if (child_table == NULL) - goto fail; - - for (i = 0; i < table_len; i++) { - /* try to parse elt i */ - ret = ec_node_parse_child(table[i], state, strvec); - if (ret < 0) - goto fail; - - if (ret == EC_PARSE_NOMATCH) - continue; - - /* build a new table without elt i */ - for (j = 0; j < table_len; j++) { - if (j < i) - child_table[j] = table[j]; - else if (j > i) - child_table[j - 1] = table[j]; - } - - /* build a new strvec (ret is the len of matched strvec) */ - len = ret; - childvec = ec_strvec_ndup(strvec, len, - ec_strvec_len(strvec) - len); - if (childvec == NULL) - goto fail; - - memset(&result, 0, sizeof(result)); - ret = __ec_node_subset_parse(&result, child_table, - table_len - 1, state, childvec); - ec_strvec_free(childvec); - childvec = NULL; - if (ret < 0) - goto fail; - - /* if result is not the best, ignore */ - if (result.parse_len < best_result.parse_len) { - memset(&result, 0, sizeof(result)); - ec_parse_del_last_child(state); - continue; - } - - /* replace the previous best result */ - ec_parse_free(best_parse); - best_parse = ec_parse_get_last_child(state); - ec_parse_unlink_child(state, best_parse); - - best_result.parse_len = result.parse_len + 1; - best_result.len = len + result.len; - - memset(&result, 0, sizeof(result)); - } - - *out = best_result; - ec_free(child_table); - if (best_parse != NULL) - ec_parse_link_child(state, best_parse); - - return 0; - - fail: - ec_parse_free(best_parse); - ec_strvec_free(childvec); - ec_free(child_table); - return -1; -} - -static int -ec_node_subset_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_subset *node = (struct ec_node_subset *)gen_node; - struct ec_parse *parse = NULL; - struct parse_result result; - int ret; - - memset(&result, 0, sizeof(result)); - - ret = __ec_node_subset_parse(&result, node->table, - node->len, state, strvec); - if (ret < 0) - goto fail; - - /* if no child node matches, return a matching empty strvec */ - if (result.parse_len == 0) - return 0; - - return result.len; - - fail: - ec_parse_free(parse); - return ret; -} - -static int -__ec_node_subset_complete(struct ec_node **table, size_t table_len, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_parse *parse = ec_comp_get_state(comp); - struct ec_strvec *childvec = NULL; - struct ec_node *save; - size_t i, len; - int ret; - - /* - * example with table = [a, b, c] - * subset_complete([a,b,c], strvec) returns: - * complete(a, strvec) + complete(b, strvec) + complete(c, strvec) + - * + __subset_complete([b, c], childvec) if a matches - * + __subset_complete([a, c], childvec) if b matches - * + __subset_complete([a, b], childvec) if c matches - */ - - /* first, try to complete with each node of the table */ - for (i = 0; i < table_len; i++) { - if (table[i] == NULL) - continue; - - ret = ec_node_complete_child(table[i], - comp, strvec); - if (ret < 0) - goto fail; - } - - /* then, if a node matches, advance in strvec and try to complete with - * all the other nodes */ - for (i = 0; i < table_len; i++) { - if (table[i] == NULL) - continue; - - ret = ec_node_parse_child(table[i], parse, strvec); - if (ret < 0) - goto fail; - - if (ret == EC_PARSE_NOMATCH) - continue; - - len = ret; - childvec = ec_strvec_ndup(strvec, len, - ec_strvec_len(strvec) - len); - if (childvec == NULL) { - ec_parse_del_last_child(parse); - goto fail; - } - - save = table[i]; - table[i] = NULL; - ret = __ec_node_subset_complete(table, table_len, - comp, childvec); - table[i] = save; - ec_strvec_free(childvec); - childvec = NULL; - ec_parse_del_last_child(parse); - - if (ret < 0) - goto fail; - } - - return 0; - -fail: - return -1; -} - -static int -ec_node_subset_complete(const struct ec_node *gen_node, - struct ec_comp *comp, - const struct ec_strvec *strvec) -{ - struct ec_node_subset *node = (struct ec_node_subset *)gen_node; - - return __ec_node_subset_complete(node->table, node->len, comp, - strvec); -} - -static void ec_node_subset_free_priv(struct ec_node *gen_node) -{ - struct ec_node_subset *node = (struct ec_node_subset *)gen_node; - size_t i; - - for (i = 0; i < node->len; i++) - ec_node_free(node->table[i]); - ec_free(node->table); -} - -static size_t -ec_node_subset_get_children_count(const struct ec_node *gen_node) -{ - struct ec_node_subset *node = (struct ec_node_subset *)gen_node; - return node->len; -} - -static int -ec_node_subset_get_child(const struct ec_node *gen_node, size_t i, - struct ec_node **child, unsigned int *refs) -{ - struct ec_node_subset *node = (struct ec_node_subset *)gen_node; - - if (i >= node->len) - return -1; - - *child = node->table[i]; - *refs = 1; - return 0; -} - -static struct ec_node_type ec_node_subset_type = { - .name = "subset", - .parse = ec_node_subset_parse, - .complete = ec_node_subset_complete, - .size = sizeof(struct ec_node_subset), - .free_priv = ec_node_subset_free_priv, - .get_children_count = ec_node_subset_get_children_count, - .get_child = ec_node_subset_get_child, -}; - -EC_NODE_TYPE_REGISTER(ec_node_subset_type); - -int ec_node_subset_add(struct ec_node *gen_node, struct ec_node *child) -{ - struct ec_node_subset *node = (struct ec_node_subset *)gen_node; - struct ec_node **table; - - assert(node != NULL); // XXX specific assert for it, like in libyang - - if (child == NULL) { - errno = EINVAL; - goto fail; - } - - if (ec_node_check_type(gen_node, &ec_node_subset_type) < 0) - goto fail; - - table = ec_realloc(node->table, (node->len + 1) * sizeof(*node->table)); - if (table == NULL) { - ec_node_free(child); - return -1; - } - - node->table = table; - table[node->len] = child; - node->len++; - - return 0; - -fail: - ec_node_free(child); - return -1; -} - -struct ec_node *__ec_node_subset(const char *id, ...) -{ - struct ec_node *gen_node = NULL; - struct ec_node_subset *node = NULL; - struct ec_node *child; - va_list ap; - int fail = 0; - - va_start(ap, id); - - gen_node = ec_node_from_type(&ec_node_subset_type, id); - node = (struct ec_node_subset *)gen_node; - if (node == NULL) - fail = 1;; - - for (child = va_arg(ap, struct ec_node *); - child != EC_NODE_ENDLIST; - child = va_arg(ap, struct ec_node *)) { - - /* on error, don't quit the loop to avoid leaks */ - if (fail == 1 || child == NULL || - ec_node_subset_add(gen_node, child) < 0) { - fail = 1; - ec_node_free(child); - } - } - - if (fail == 1) - goto fail; - - va_end(ap); - return gen_node; - -fail: - ec_node_free(gen_node); /* will also free children */ - va_end(ap); - return NULL; -} - -/* LCOV_EXCL_START */ -static int ec_node_subset_testcase(void) -{ - struct ec_node *node; - int testres = 0; - - node = EC_NODE_SUBSET(EC_NO_ID, - EC_NODE_OR(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_str(EC_NO_ID, "bar")), - ec_node_str(EC_NO_ID, "bar"), - ec_node_str(EC_NO_ID, "toto") - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_PARSE(node, 0); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "titi"); - testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "toto"); - testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "bar"); - testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo"); - testres |= EC_TEST_CHECK_PARSE(node, 0, " "); - testres |= EC_TEST_CHECK_PARSE(node, 0, "foox"); - ec_node_free(node); - - /* test completion */ - node = EC_NODE_SUBSET(EC_NO_ID, - ec_node_str(EC_NO_ID, "foo"), - ec_node_str(EC_NO_ID, "bar"), - ec_node_str(EC_NO_ID, "bar2"), - ec_node_str(EC_NO_ID, "toto"), - ec_node_str(EC_NO_ID, "titi") - ); - if (node == NULL) { - EC_LOG(EC_LOG_ERR, "cannot create node\n"); - return -1; - } - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "", EC_NODE_ENDLIST, - "bar2", "bar", "foo", "toto", "titi", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "bar", "bar2", "", EC_NODE_ENDLIST, - "foo", "toto", "titi", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "f", EC_NODE_ENDLIST, - "foo", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "b", EC_NODE_ENDLIST, - "bar", "bar2", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "bar", EC_NODE_ENDLIST, - "bar", "bar2", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "bar", "b", EC_NODE_ENDLIST, - "bar2", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "t", EC_NODE_ENDLIST, - "toto", "titi", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "to", EC_NODE_ENDLIST, - "toto", EC_NODE_ENDLIST); - testres |= EC_TEST_CHECK_COMPLETE(node, - "x", EC_NODE_ENDLIST, - EC_NODE_ENDLIST); - ec_node_free(node); - - return testres; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_subset_test = { - .name = "node_subset", - .test = ec_node_subset_testcase, -}; - -EC_TEST_REGISTER(ec_node_subset_test); diff --git a/lib/ecoli_node_subset.h b/lib/ecoli_node_subset.h deleted file mode 100644 index 734b1ae..0000000 --- a/lib/ecoli_node_subset.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_NODE_SUBSET_ -#define ECOLI_NODE_SUBSET_ - -#include - -#define EC_NODE_SUBSET(args...) __ec_node_subset(args, EC_NODE_ENDLIST) - -/* list must be terminated with EC_NODE_ENDLIST */ -/* all nodes given in the list will be freed when freeing this one */ -/* avoid using this function directly, prefer the macro EC_NODE_SUBSET() or - * ec_node_subset() + ec_node_subset_add() */ -struct ec_node *__ec_node_subset(const char *id, ...); - -struct ec_node *ec_node_subset(const char *id); - -/* child is consumed */ -int ec_node_subset_add(struct ec_node *node, struct ec_node *child); - -#endif diff --git a/lib/ecoli_parse.c b/lib/ecoli_parse.c deleted file mode 100644 index 6396fc1..0000000 --- a/lib/ecoli_parse.c +++ /dev/null @@ -1,540 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(parse); - -TAILQ_HEAD(ec_parse_list, ec_parse); - -struct ec_parse { - TAILQ_ENTRY(ec_parse) next; - struct ec_parse_list children; - struct ec_parse *parent; - const struct ec_node *node; - struct ec_strvec *strvec; - struct ec_keyval *attrs; -}; - -static int __ec_node_parse_child(const struct ec_node *node, - struct ec_parse *state, - bool is_root, const struct ec_strvec *strvec) -{ - struct ec_strvec *match_strvec; - struct ec_parse *child = NULL; - int ret; - - if (ec_node_type(node)->parse == NULL) { - errno = ENOTSUP; - return -1; - } - - if (!is_root) { - child = ec_parse(node); - if (child == NULL) - return -1; - - ec_parse_link_child(state, child); - } else { - child = state; - } - ret = ec_node_type(node)->parse(node, child, strvec); - if (ret < 0) - goto fail; - - if (ret == EC_PARSE_NOMATCH) { - if (!is_root) { - ec_parse_unlink_child(state, child); - ec_parse_free(child); - } - return ret; - } - - match_strvec = ec_strvec_ndup(strvec, 0, ret); - if (match_strvec == NULL) - goto fail; - - child->strvec = match_strvec; - - return ret; - -fail: - if (!is_root) { - ec_parse_unlink_child(state, child); - ec_parse_free(child); - } - return -1; -} - -int ec_node_parse_child(const struct ec_node *node, struct ec_parse *state, - const struct ec_strvec *strvec) -{ - assert(state != NULL); - return __ec_node_parse_child(node, state, false, strvec); -} - -struct ec_parse *ec_node_parse_strvec(const struct ec_node *node, - const struct ec_strvec *strvec) -{ - struct ec_parse *parse = ec_parse(node); - int ret; - - if (parse == NULL) - return NULL; - - ret = __ec_node_parse_child(node, parse, true, strvec); - if (ret < 0) { - ec_parse_free(parse); - return NULL; - } - - return parse; -} - -struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str) -{ - struct ec_strvec *strvec = NULL; - struct ec_parse *parse = NULL; - - errno = ENOMEM; - strvec = ec_strvec(); - if (strvec == NULL) - goto fail; - - if (ec_strvec_add(strvec, str) < 0) - goto fail; - - parse = ec_node_parse_strvec(node, strvec); - if (parse == NULL) - goto fail; - - ec_strvec_free(strvec); - return parse; - - fail: - ec_strvec_free(strvec); - ec_parse_free(parse); - return NULL; -} - -struct ec_parse *ec_parse(const struct ec_node *node) -{ - struct ec_parse *parse = NULL; - - parse = ec_calloc(1, sizeof(*parse)); - if (parse == NULL) - goto fail; - - TAILQ_INIT(&parse->children); - - parse->node = node; - parse->attrs = ec_keyval(); - if (parse->attrs == NULL) - goto fail; - - return parse; - - fail: - if (parse != NULL) - ec_keyval_free(parse->attrs); - ec_free(parse); - - return NULL; -} - -static struct ec_parse * -__ec_parse_dup(const struct ec_parse *root, const struct ec_parse *ref, - struct ec_parse **new_ref) -{ - struct ec_parse *dup = NULL; - struct ec_parse *child, *dup_child; - struct ec_keyval *attrs = NULL; - - if (root == NULL) - return NULL; - - dup = ec_parse(root->node); - if (dup == NULL) - return NULL; - - if (root == ref) - *new_ref = dup; - - attrs = ec_keyval_dup(root->attrs); - if (attrs == NULL) - goto fail; - ec_keyval_free(dup->attrs); - dup->attrs = attrs; - - if (root->strvec != NULL) { - dup->strvec = ec_strvec_dup(root->strvec); - if (dup->strvec == NULL) - goto fail; - } - - TAILQ_FOREACH(child, &root->children, next) { - dup_child = __ec_parse_dup(child, ref, new_ref); - if (dup_child == NULL) - goto fail; - ec_parse_link_child(dup, dup_child); - } - - return dup; - -fail: - ec_parse_free(dup); - return NULL; -} - -struct ec_parse *ec_parse_dup(const struct ec_parse *parse) -{ - const struct ec_parse *root; - struct ec_parse *dup_root, *dup = NULL; - - root = ec_parse_get_root(parse); - dup_root = __ec_parse_dup(root, parse, &dup); - if (dup_root == NULL) - return NULL; - assert(dup != NULL); - - return dup; -} - -void ec_parse_free_children(struct ec_parse *parse) -{ - struct ec_parse *child; - - if (parse == NULL) - return; - - while (!TAILQ_EMPTY(&parse->children)) { - child = TAILQ_FIRST(&parse->children); - TAILQ_REMOVE(&parse->children, child, next); - child->parent = NULL; - ec_parse_free(child); - } -} - -void ec_parse_free(struct ec_parse *parse) -{ - if (parse == NULL) - return; - - ec_assert_print(parse->parent == NULL, - "parent not NULL in ec_parse_free()"); - - ec_parse_free_children(parse); - ec_strvec_free(parse->strvec); - ec_keyval_free(parse->attrs); - ec_free(parse); -} - -static void __ec_parse_dump(FILE *out, - const struct ec_parse *parse, size_t indent) -{ - struct ec_parse *child; - const struct ec_strvec *vec; - const char *id = "none", *typename = "none"; - - /* node can be null when parsing is incomplete */ - if (parse->node != NULL) { - id = parse->node->id; - typename = ec_node_type(parse->node)->name; - } - - fprintf(out, "%*s" "type=%s id=%s vec=", - (int)indent * 4, "", typename, id); - vec = ec_parse_strvec(parse); - ec_strvec_dump(out, vec); - - TAILQ_FOREACH(child, &parse->children, next) - __ec_parse_dump(out, child, indent + 1); -} - -void ec_parse_dump(FILE *out, const struct ec_parse *parse) -{ - fprintf(out, "------------------- parse dump:\n"); - - if (parse == NULL) { - fprintf(out, "parse is NULL\n"); - return; - } - - /* only exist if it does not match (strvec == NULL) and if it - * does not have children: an incomplete parse, like those - * generated by complete() don't match but have children that - * may match. */ - if (!ec_parse_matches(parse) && TAILQ_EMPTY(&parse->children)) { - fprintf(out, "no match\n"); - return; - } - - __ec_parse_dump(out, parse, 0); -} - -void ec_parse_link_child(struct ec_parse *parse, - struct ec_parse *child) -{ - TAILQ_INSERT_TAIL(&parse->children, child, next); - child->parent = parse; -} - -void ec_parse_unlink_child(struct ec_parse *parse, - struct ec_parse *child) -{ - TAILQ_REMOVE(&parse->children, child, next); - child->parent = NULL; -} - -struct ec_parse * -ec_parse_get_first_child(const struct ec_parse *parse) -{ - return TAILQ_FIRST(&parse->children); -} - -struct ec_parse * -ec_parse_get_last_child(const struct ec_parse *parse) -{ - return TAILQ_LAST(&parse->children, ec_parse_list); -} - -struct ec_parse *ec_parse_get_next(const struct ec_parse *parse) -{ - return TAILQ_NEXT(parse, next); -} - -bool ec_parse_has_child(const struct ec_parse *parse) -{ - return !TAILQ_EMPTY(&parse->children); -} - -const struct ec_node *ec_parse_get_node(const struct ec_parse *parse) -{ - return parse->node; -} - -void ec_parse_del_last_child(struct ec_parse *parse) -{ - struct ec_parse *child; - - child = ec_parse_get_last_child(parse); - ec_parse_unlink_child(parse, child); - ec_parse_free(child); -} - -struct ec_parse *__ec_parse_get_root(struct ec_parse *parse) -{ - if (parse == NULL) - return NULL; - - while (parse->parent != NULL) - parse = parse->parent; - - return parse; -} - -struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse) -{ - if (parse == NULL) - return NULL; - - return parse->parent; -} - -struct ec_parse *ec_parse_iter_next(struct ec_parse *parse) -{ - struct ec_parse *child, *parent, *next; - - child = TAILQ_FIRST(&parse->children); - if (child != NULL) - return child; - parent = parse->parent; - while (parent != NULL) { - next = TAILQ_NEXT(parse, next); - if (next != NULL) - return next; - parse = parent; - parent = parse->parent; - } - return NULL; -} - -struct ec_parse *ec_parse_find_first(struct ec_parse *parse, - const char *id) -{ - struct ec_parse *iter; - - if (parse == NULL) - return NULL; - - for (iter = parse; iter != NULL; iter = ec_parse_iter_next(iter)) { - if (iter->node != NULL && - iter->node->id != NULL && - !strcmp(iter->node->id, id)) - return iter; - } - - return NULL; -} - -struct ec_keyval * -ec_parse_get_attrs(struct ec_parse *parse) -{ - if (parse == NULL) - return NULL; - - return parse->attrs; -} - -const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse) -{ - if (parse == NULL || parse->strvec == NULL) - return NULL; - - return parse->strvec; -} - -/* number of strings in the parse vector */ -size_t ec_parse_len(const struct ec_parse *parse) -{ - if (parse == NULL || parse->strvec == NULL) - return 0; - - return ec_strvec_len(parse->strvec); -} - -size_t ec_parse_matches(const struct ec_parse *parse) -{ - if (parse == NULL) - return 0; - - if (parse->strvec == NULL) - return 0; - - return 1; -} - -/* LCOV_EXCL_START */ -static int ec_parse_testcase(void) -{ - struct ec_node *node = NULL; - struct ec_parse *p = NULL, *p2 = NULL; - const struct ec_parse *pc; - FILE *f = NULL; - char *buf = NULL; - size_t buflen = 0; - int testres = 0; - int ret; - - node = ec_node_sh_lex(EC_NO_ID, - EC_NODE_SEQ(EC_NO_ID, - ec_node_str("id_x", "x"), - ec_node_str("id_y", "y"))); - if (node == NULL) - goto fail; - - p = ec_node_parse(node, "xcdscds"); - testres |= EC_TEST_CHECK( - p != NULL && !ec_parse_matches(p), - "parse should not match\n"); - - f = open_memstream(&buf, &buflen); - if (f == NULL) - goto fail; - ec_parse_dump(f, p); - fclose(f); - f = NULL; - - testres |= EC_TEST_CHECK( - strstr(buf, "no match"), "bad dump\n"); - free(buf); - buf = NULL; - ec_parse_free(p); - - p = ec_node_parse(node, "x y"); - testres |= EC_TEST_CHECK( - p != NULL && ec_parse_matches(p), - "parse should match\n"); - testres |= EC_TEST_CHECK( - ec_parse_len(p) == 1, "bad parse len\n"); - - ret = ec_keyval_set(ec_parse_get_attrs(p), "key", "val", NULL); - testres |= EC_TEST_CHECK(ret == 0, - "cannot set parse attribute\n"); - - p2 = ec_parse_dup(p); - testres |= EC_TEST_CHECK( - p2 != NULL && ec_parse_matches(p2), - "parse should match\n"); - ec_parse_free(p2); - p2 = NULL; - - pc = ec_parse_find_first(p, "id_x"); - testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_x"); - testres |= EC_TEST_CHECK(pc != NULL && - ec_parse_get_parent(pc) != NULL && - ec_parse_get_parent(ec_parse_get_parent(pc)) == p, - "invalid parent\n"); - - pc = ec_parse_find_first(p, "id_y"); - testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_y"); - pc = ec_parse_find_first(p, "id_dezdezdez"); - testres |= EC_TEST_CHECK(pc == NULL, "should not find bad id"); - - - f = open_memstream(&buf, &buflen); - if (f == NULL) - goto fail; - ec_parse_dump(f, p); - fclose(f); - f = NULL; - - testres |= EC_TEST_CHECK( - strstr(buf, "type=sh_lex id=no-id") && - strstr(buf, "type=seq id=no-id") && - strstr(buf, "type=str id=id_x") && - strstr(buf, "type=str id=id_x"), - "bad dump\n"); - free(buf); - buf = NULL; - - ec_parse_free(p); - ec_node_free(node); - return testres; - -fail: - ec_parse_free(p2); - ec_parse_free(p); - ec_node_free(node); - if (f != NULL) - fclose(f); - free(buf); - - return -1; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_parse_test = { - .name = "parse", - .test = ec_parse_testcase, -}; - -EC_TEST_REGISTER(ec_parse_test); diff --git a/lib/ecoli_parse.h b/lib/ecoli_parse.h deleted file mode 100644 index 79e644f..0000000 --- a/lib/ecoli_parse.h +++ /dev/null @@ -1,237 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 -#include -#include -#include - -struct ec_node; -struct ec_parse; - -/** - * Create an empty parse tree. - * - * @return - * The empty parse tree. - */ -struct ec_parse *ec_parse(const struct ec_node *node); - -/** - * - * - * - */ -void ec_parse_free(struct ec_parse *parse); - -/** - * - * - * - */ -void ec_parse_free_children(struct ec_parse *parse); - -/** - * - * - * - */ -struct ec_parse *ec_parse_dup(const struct ec_parse *parse); - -/** - * - * - * - */ -const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse); - -/* a NULL return value is an error, with errno set - ENOTSUP: no ->parse() operation -*/ -/** - * - * - * - */ -struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str); - -/** - * - * - * - */ -struct ec_parse *ec_node_parse_strvec(const struct ec_node *node, - const struct ec_strvec *strvec); - -/** - * - * - * - */ -#define EC_PARSE_NOMATCH INT_MAX - -/* internal: used by nodes - * - * state is the current parse tree, which is built piece by piece while - * parsing the node tree: ec_node_parse_child() creates a new child in - * this state parse tree, and calls the parse() method for the child - * node, with state pointing to this new child. If it does not match, - * the child is removed in the state, else it is kept, with its - * possible descendants. - * - * return: - * the number of matched strings in strvec on success - * EC_PARSE_NOMATCH (positive) if it does not match - * -1 on error, and errno is set - */ -int ec_node_parse_child(const struct ec_node *node, - struct ec_parse *state, - const struct ec_strvec *strvec); - -/** - * - * - * - */ -void ec_parse_link_child(struct ec_parse *parse, - struct ec_parse *child); -/** - * - * - * - */ -void ec_parse_unlink_child(struct ec_parse *parse, - struct ec_parse *child); - -/* keep the const */ -#define ec_parse_get_root(parse) ({ \ - const struct ec_parse *p_ = parse; /* check type */ \ - struct ec_parse *parse_ = (struct ec_parse *)parse; \ - typeof(parse) res_; \ - (void)p_; \ - res_ = __ec_parse_get_root(parse_); \ - res_; \ -}) - -/** - * - * - * - */ -struct ec_parse *__ec_parse_get_root(struct ec_parse *parse); - -/** - * - * - * - */ -struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse); - -/** - * Get the first child of a tree. - * - */ -struct ec_parse *ec_parse_get_first_child(const struct ec_parse *parse); - -/** - * - * - * - */ -struct ec_parse *ec_parse_get_last_child(const struct ec_parse *parse); - -/** - * - * - * - */ -struct ec_parse *ec_parse_get_next(const struct ec_parse *parse); - -/** - * - * - * - */ -#define EC_PARSE_FOREACH_CHILD(child, parse) \ - for (child = ec_parse_get_first_child(parse); \ - child != NULL; \ - child = ec_parse_get_next(child)) \ - -/** - * - * - * - */ -bool ec_parse_has_child(const struct ec_parse *parse); - -/** - * - * - * - */ -const struct ec_node *ec_parse_get_node(const struct ec_parse *parse); - -/** - * - * - * - */ -void ec_parse_del_last_child(struct ec_parse *parse); - -/** - * - * - * - */ -struct ec_keyval *ec_parse_get_attrs(struct ec_parse *parse); - -/** - * - * - * - */ -void ec_parse_dump(FILE *out, const struct ec_parse *parse); - -/** - * - * - * - */ -struct ec_parse *ec_parse_find_first(struct ec_parse *parse, - const char *id); - -/** - * Iterate among parse tree - * - * Use it with: - * for (iter = state; iter != NULL; iter = ec_parse_iter_next(iter)) - */ -struct ec_parse *ec_parse_iter_next(struct ec_parse *parse); - -/** - * - * - * - */ -size_t ec_parse_len(const struct ec_parse *parse); - -/** - * - * - * - */ -size_t ec_parse_matches(const struct ec_parse *parse); - -#endif diff --git a/lib/ecoli_string.c b/lib/ecoli_string.c deleted file mode 100644 index 7723818..0000000 --- a/lib/ecoli_string.c +++ /dev/null @@ -1,78 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include - -#include -#include -#include - -/* count the number of identical chars at the beginning of 2 strings */ -size_t ec_strcmp_count(const char *s1, const char *s2) -{ - size_t i = 0; - - while (s1[i] && s2[i] && s1[i] == s2[i]) - i++; - - return i; -} - -int ec_str_startswith(const char *s, const char *beginning) -{ - size_t len; - - len = ec_strcmp_count(s, beginning); - if (beginning[len] == '\0') - return 1; - - return 0; -} - -int ec_vasprintf(char **buf, const char *fmt, va_list ap) -{ - char dummy; - int buflen, ret; - va_list aq; - - va_copy(aq, ap); - *buf = NULL; - ret = vsnprintf(&dummy, 1, fmt, aq); - va_end(aq); - if (ret < 0) - return ret; - - buflen = ret + 1; - *buf = ec_malloc(buflen); - if (*buf == NULL) - return -1; - - va_copy(aq, ap); - ret = vsnprintf(*buf, buflen, fmt, aq); - va_end(aq); - - ec_assert_print(ret < buflen, "invalid return value for vsnprintf"); - if (ret < 0) { - free(*buf); - *buf = NULL; - return -1; - } - - return ret; -} - -int ec_asprintf(char **buf, const char *fmt, ...) -{ - va_list ap; - int ret; - - va_start(ap, fmt); - ret = ec_vasprintf(buf, fmt, ap); - va_end(ap); - - return ret; -} diff --git a/lib/ecoli_string.h b/lib/ecoli_string.h deleted file mode 100644 index add73a0..0000000 --- a/lib/ecoli_string.h +++ /dev/null @@ -1,22 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_STRING_ -#define ECOLI_STRING_ - -#include - -/* count the number of identical chars at the beginning of 2 strings */ -size_t ec_strcmp_count(const char *s1, const char *s2); - -/* return 1 if 's' starts with 'beginning' */ -int ec_str_startswith(const char *s, const char *beginning); - -/* like asprintf, but use libecoli allocator */ -int ec_asprintf(char **buf, const char *fmt, ...); - -/* like vasprintf, but use libecoli allocator */ -int ec_vasprintf(char **buf, const char *fmt, va_list ap); - -#endif diff --git a/lib/ecoli_strvec.c b/lib/ecoli_strvec.c deleted file mode 100644 index c574b1b..0000000 --- a/lib/ecoli_strvec.c +++ /dev/null @@ -1,427 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#define _GNU_SOURCE /* qsort_r */ -#include -#include -#include -#include - -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(strvec); - -struct ec_strvec_elt { - unsigned int refcnt; - char *str; -}; - -struct ec_strvec { - size_t len; - struct ec_strvec_elt **vec; -}; - -struct ec_strvec *ec_strvec(void) -{ - struct ec_strvec *strvec; - - strvec = ec_calloc(1, sizeof(*strvec)); - if (strvec == NULL) - return NULL; - - return strvec; -} - -int ec_strvec_add(struct ec_strvec *strvec, const char *s) -{ - struct ec_strvec_elt *elt, **new_vec; - - new_vec = ec_realloc(strvec->vec, - sizeof(*strvec->vec) * (strvec->len + 1)); - if (new_vec == NULL) - return -1; - - strvec->vec = new_vec; - - elt = ec_malloc(sizeof(*elt)); - if (elt == NULL) - return -1; - - elt->str = ec_strdup(s); - if (elt->str == NULL) { - ec_free(elt); - return -1; - } - elt->refcnt = 1; - - new_vec[strvec->len] = elt; - strvec->len++; - return 0; -} - -struct ec_strvec *ec_strvec_from_array(const char * const *strarr, - size_t n) -{ - struct ec_strvec *strvec = NULL; - size_t i; - - strvec = ec_strvec(); - if (strvec == NULL) - goto fail; - - for (i = 0; i < n; i++) { - if (ec_strvec_add(strvec, strarr[i]) < 0) - goto fail; - } - - return strvec; - -fail: - ec_strvec_free(strvec); - return NULL; -} - -int ec_strvec_del_last(struct ec_strvec *strvec) -{ - struct ec_strvec_elt *elt; - - if (strvec->len == 0) { - errno = EINVAL; - return -1; - } - - elt = strvec->vec[strvec->len - 1]; - elt->refcnt--; - if (elt->refcnt == 0) { - ec_free(elt->str); - ec_free(elt); - } - strvec->len--; - return 0; -} - -struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t off, - size_t len) -{ - struct ec_strvec *copy = NULL; - size_t i, veclen; - - veclen = ec_strvec_len(strvec); - if (off + len > veclen) - return NULL; - - copy = ec_strvec(); - if (copy == NULL) - goto fail; - - if (len == 0) - return copy; - - copy->vec = ec_calloc(len, sizeof(*copy->vec)); - if (copy->vec == NULL) - goto fail; - - for (i = 0; i < len; i++) { - copy->vec[i] = strvec->vec[i + off]; - copy->vec[i]->refcnt++; - } - copy->len = len; - - return copy; - -fail: - ec_strvec_free(copy); - return NULL; -} - -struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec) -{ - return ec_strvec_ndup(strvec, 0, ec_strvec_len(strvec)); -} - -void ec_strvec_free(struct ec_strvec *strvec) -{ - struct ec_strvec_elt *elt; - size_t i; - - if (strvec == NULL) - return; - - for (i = 0; i < ec_strvec_len(strvec); i++) { - elt = strvec->vec[i]; - elt->refcnt--; - if (elt->refcnt == 0) { - ec_free(elt->str); - ec_free(elt); - } - } - - ec_free(strvec->vec); - ec_free(strvec); -} - -size_t ec_strvec_len(const struct ec_strvec *strvec) -{ - return strvec->len; -} - -const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx) -{ - if (strvec == NULL || idx >= strvec->len) - return NULL; - - return strvec->vec[idx]->str; -} - -int ec_strvec_cmp(const struct ec_strvec *strvec1, - const struct ec_strvec *strvec2) -{ - size_t i; - - if (ec_strvec_len(strvec1) != ec_strvec_len(strvec2)) - return -1; - - for (i = 0; i < ec_strvec_len(strvec1); i++) { - if (strcmp(ec_strvec_val(strvec1, i), - ec_strvec_val(strvec2, i))) - return -1; - } - - return 0; -} - -static int -cmp_vec_elt(const void *p1, const void *p2, void *arg) -{ - int (*str_cmp)(const char *s1, const char *s2) = arg; - const struct ec_strvec_elt * const *e1 = p1, * const *e2 = p2; - - return str_cmp((*e1)->str, (*e2)->str); -} - -void ec_strvec_sort(struct ec_strvec *strvec, - int (*str_cmp)(const char *s1, const char *s2)) -{ - if (str_cmp == NULL) - str_cmp = strcmp; - qsort_r(strvec->vec, ec_strvec_len(strvec), - sizeof(*strvec->vec), cmp_vec_elt, str_cmp); -} - -void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec) -{ - size_t i; - - if (strvec == NULL) { - fprintf(out, "none\n"); - return; - } - - fprintf(out, "strvec (len=%zu) [", strvec->len); - for (i = 0; i < ec_strvec_len(strvec); i++) { - if (i == 0) - fprintf(out, "%s", strvec->vec[i]->str); - else - fprintf(out, ", %s", strvec->vec[i]->str); - } - fprintf(out, "]\n"); - -} - -/* LCOV_EXCL_START */ -static int ec_strvec_testcase(void) -{ - struct ec_strvec *strvec = NULL; - struct ec_strvec *strvec2 = NULL; - FILE *f = NULL; - char *buf = NULL; - size_t buflen = 0; - int testres = 0; - - strvec = ec_strvec(); - if (strvec == NULL) { - EC_TEST_ERR("cannot create strvec\n"); - goto fail; - } - if (ec_strvec_len(strvec) != 0) { - EC_TEST_ERR("bad strvec len (0)\n"); - goto fail; - } - if (ec_strvec_add(strvec, "0") < 0) { - EC_TEST_ERR("cannot add (0) in strvec\n"); - goto fail; - } - if (ec_strvec_len(strvec) != 1) { - EC_TEST_ERR("bad strvec len (1)\n"); - goto fail; - } - if (ec_strvec_add(strvec, "1") < 0) { - EC_TEST_ERR("cannot add (1) in strvec\n"); - goto fail; - } - if (ec_strvec_len(strvec) != 2) { - EC_TEST_ERR("bad strvec len (2)\n"); - goto fail; - } - if (strcmp(ec_strvec_val(strvec, 0), "0")) { - EC_TEST_ERR("invalid element in strvec (0)\n"); - goto fail; - } - if (strcmp(ec_strvec_val(strvec, 1), "1")) { - EC_TEST_ERR("invalid element in strvec (1)\n"); - goto fail; - } - if (ec_strvec_val(strvec, 2) != NULL) { - EC_TEST_ERR("strvec val should be NULL\n"); - goto fail; - } - - strvec2 = ec_strvec_dup(strvec); - if (strvec2 == NULL) { - EC_TEST_ERR("cannot create strvec2\n"); - goto fail; - } - if (ec_strvec_len(strvec2) != 2) { - EC_TEST_ERR("bad strvec2 len (2)\n"); - goto fail; - } - if (strcmp(ec_strvec_val(strvec2, 0), "0")) { - EC_TEST_ERR("invalid element in strvec2 (0)\n"); - goto fail; - } - if (strcmp(ec_strvec_val(strvec2, 1), "1")) { - EC_TEST_ERR("invalid element in strvec2 (1)\n"); - goto fail; - } - if (ec_strvec_val(strvec2, 2) != NULL) { - EC_TEST_ERR("strvec2 val should be NULL\n"); - goto fail; - } - ec_strvec_free(strvec2); - - strvec2 = ec_strvec_ndup(strvec, 0, 0); - if (strvec2 == NULL) { - EC_TEST_ERR("cannot create strvec2\n"); - goto fail; - } - if (ec_strvec_len(strvec2) != 0) { - EC_TEST_ERR("bad strvec2 len (0)\n"); - goto fail; - } - if (ec_strvec_val(strvec2, 0) != NULL) { - EC_TEST_ERR("strvec2 val should be NULL\n"); - goto fail; - } - ec_strvec_free(strvec2); - - strvec2 = ec_strvec_ndup(strvec, 1, 1); - if (strvec2 == NULL) { - EC_TEST_ERR("cannot create strvec2\n"); - goto fail; - } - if (ec_strvec_len(strvec2) != 1) { - EC_TEST_ERR("bad strvec2 len (1)\n"); - goto fail; - } - if (strcmp(ec_strvec_val(strvec2, 0), "1")) { - EC_TEST_ERR("invalid element in strvec2 (1)\n"); - goto fail; - } - if (ec_strvec_val(strvec2, 1) != NULL) { - EC_TEST_ERR("strvec2 val should be NULL\n"); - goto fail; - } - ec_strvec_free(strvec2); - - strvec2 = ec_strvec_ndup(strvec, 3, 1); - if (strvec2 != NULL) { - EC_TEST_ERR("strvec2 should be NULL\n"); - goto fail; - } - ec_strvec_free(strvec2); - - strvec2 = EC_STRVEC("0", "1"); - if (strvec2 == NULL) { - EC_TEST_ERR("cannot create strvec from array\n"); - goto fail; - } - testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0, - "strvec and strvec2 should be equal\n"); - ec_strvec_free(strvec2); - - f = open_memstream(&buf, &buflen); - if (f == NULL) - goto fail; - ec_strvec_dump(f, strvec); - fclose(f); - f = NULL; - testres |= EC_TEST_CHECK( - strstr(buf, "strvec (len=2) [0, 1]"), "bad dump\n"); - free(buf); - buf = NULL; - - ec_strvec_del_last(strvec); - strvec2 = EC_STRVEC("0"); - if (strvec2 == NULL) { - EC_TEST_ERR("cannot create strvec from array\n"); - goto fail; - } - testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0, - "strvec and strvec2 should be equal\n"); - ec_strvec_free(strvec2); - strvec2 = NULL; - - f = open_memstream(&buf, &buflen); - if (f == NULL) - goto fail; - ec_strvec_dump(f, NULL); - fclose(f); - f = NULL; - testres |= EC_TEST_CHECK( - strstr(buf, "none"), "bad dump\n"); - free(buf); - buf = NULL; - - ec_strvec_free(strvec); - - strvec = EC_STRVEC("e", "a", "f", "d", "b", "c"); - if (strvec == NULL) { - EC_TEST_ERR("cannot create strvec from array\n"); - goto fail; - } - ec_strvec_sort(strvec, NULL); - strvec2 = EC_STRVEC("a", "b", "c", "d", "e", "f"); - if (strvec2 == NULL) { - EC_TEST_ERR("cannot create strvec from array\n"); - goto fail; - } - testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0, - "strvec and strvec2 should be equal\n"); - ec_strvec_free(strvec); - strvec = NULL; - ec_strvec_free(strvec2); - strvec2 = NULL; - - return testres; - -fail: - if (f != NULL) - fclose(f); - ec_strvec_free(strvec); - ec_strvec_free(strvec2); - free(buf); - - return -1; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_node_str_test = { - .name = "strvec", - .test = ec_strvec_testcase, -}; - -EC_TEST_REGISTER(ec_node_str_test); diff --git a/lib/ecoli_strvec.h b/lib/ecoli_strvec.h deleted file mode 100644 index 8e14973..0000000 --- a/lib/ecoli_strvec.h +++ /dev/null @@ -1,174 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * 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 - -/** - * Allocate a new empty string vector. - * - * @return - * The new strvec object, or NULL on error (errno is set). - */ -struct ec_strvec *ec_strvec(void); - -#ifndef EC_COUNT_OF -#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \ - ((size_t)(!(sizeof(x) % sizeof(0[x]))))) -#endif - -/** - * Allocate a new string vector - * - * The string vector is initialized with the list of const strings - * passed as arguments. - * - * @return - * The new strvec object, or NULL on error (errno is set). - */ -#define EC_STRVEC(args...) ({ \ - const char *_arr[] = {args}; \ - ec_strvec_from_array(_arr, EC_COUNT_OF(_arr)); \ - }) -/** - * Allocate a new string vector - * - * The string vector is initialized with the array of const strings - * passed as arguments. - * - * @param strarr - * The array of const strings. - * @param n - * The number of strings in the array. - * @return - * The new strvec object, or NULL on error (errno is set). - */ -struct ec_strvec *ec_strvec_from_array(const char * const *strarr, - size_t n); - -/** - * Add a string in a vector. - * - * @param strvec - * The pointer to the string vector. - * @param s - * The string to be added at the end of the vector. - * @return - * 0 on success or -1 on error (errno is set). - */ -int ec_strvec_add(struct ec_strvec *strvec, const char *s); - -/** - * Delete the last entry in the string vector. - * - * @param strvec - * The pointer to the string vector. - * @param s - * The string to be added at the end of the vector. - * @return - * 0 on success or -1 on error (errno is set). - */ -int ec_strvec_del_last(struct ec_strvec *strvec); - -/** - * Duplicate a string vector. - * - * @param strvec - * The pointer to the string vector. - * @return - * The duplicated strvec object, or NULL on error (errno is set). - */ -struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec); - -/** - * Duplicate a part of a string vector. - * - * @param strvec - * The pointer to the string vector. - * @param off - * The index of the first string to duplicate. - * @param - * The number of strings to duplicate. - * @return - * The duplicated strvec object, or NULL on error (errno is set). - */ -struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, - size_t off, size_t len); - -/** - * Free a string vector. - * - * @param strvec - * The pointer to the string vector. - */ -void ec_strvec_free(struct ec_strvec *strvec); - -/** - * Get the length of a string vector. - * - * @param strvec - * The pointer to the string vector. - * @return - * The length of the vector. - */ -size_t ec_strvec_len(const struct ec_strvec *strvec); - -/** - * Get a string element from a vector. - * - * @param strvec - * The pointer to the string vector. - * @param idx - * The index of the string to get. - * @return - * The string stored at given index, or NULL on error (strvec is NULL - * or invalid index). - */ -const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx); - -/** - * Compare two string vectors - * - * @param strvec - * The pointer to the first string vector. - * @param strvec - * The pointer to the second string vector. - * @return - * 0 if the string vectors are equal. - */ -int ec_strvec_cmp(const struct ec_strvec *strvec1, - const struct ec_strvec *strvec2); - -/** - * Sort the string vector. - * - * @param strvec - * The pointer to the first string vector. - * @param str_cmp - * The sort function to use. If NULL, use strcmp. - */ -void ec_strvec_sort(struct ec_strvec *strvec, - int (*str_cmp)(const char *s1, const char *s2)); - -/** - * Dump a string vector. - * - * @param out - * The stream where the dump is sent. - * @param strvec - * The pointer to the string vector. - */ -void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec); - -#endif diff --git a/lib/ecoli_test.c b/lib/ecoli_test.c deleted file mode 100644 index b090bd3..0000000 --- a/lib/ecoli_test.c +++ /dev/null @@ -1,217 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -static struct ec_test_list test_list = TAILQ_HEAD_INITIALIZER(test_list); - -EC_LOG_TYPE_REGISTER(test); - -static struct ec_test *ec_test_lookup(const char *name) -{ - struct ec_test *test; - - TAILQ_FOREACH(test, &test_list, next) { - if (!strcmp(name, test->name)) - return test; - } - - errno = EEXIST; - return NULL; -} - -int ec_test_register(struct ec_test *test) -{ - if (ec_test_lookup(test->name) != NULL) - return -1; - - TAILQ_INSERT_TAIL(&test_list, test, next); - - return 0; -} - -int ec_test_check_parse(struct ec_node *tk, int expected, ...) -{ - struct ec_parse *p; - struct ec_strvec *vec = NULL; - const char *s; - int ret = -1, match; - va_list ap; - - va_start(ap, expected); - - /* build a string vector */ - vec = ec_strvec(); - if (vec == NULL) - goto out; - - for (s = va_arg(ap, const char *); - s != EC_NODE_ENDLIST; - s = va_arg(ap, const char *)) { - if (s == NULL) - goto out; - - if (ec_strvec_add(vec, s) < 0) - goto out; - } - - p = ec_node_parse_strvec(tk, vec); - if (p == NULL) { - EC_LOG(EC_LOG_ERR, "parse is NULL\n"); - } - if (ec_parse_matches(p)) - match = ec_parse_len(p); - else - match = -1; - if (expected == match) { - ret = 0; - } else { - EC_LOG(EC_LOG_ERR, - "parse len (%d) does not match expected (%d)\n", - match, expected); - } - - ec_parse_free(p); - -out: - ec_strvec_free(vec); - va_end(ap); - return ret; -} - -int ec_test_check_complete(struct ec_node *tk, enum ec_comp_type type, ...) -{ - struct ec_comp *c = NULL; - struct ec_strvec *vec = NULL; - const char *s; - int ret = 0; - unsigned int count = 0; - va_list ap; - - va_start(ap, type); - - /* build a string vector */ - vec = ec_strvec(); - if (vec == NULL) - goto out; - - for (s = va_arg(ap, const char *); - s != EC_NODE_ENDLIST; - s = va_arg(ap, const char *)) { - if (s == NULL) - goto out; - - if (ec_strvec_add(vec, s) < 0) - goto out; - } - - c = ec_node_complete_strvec(tk, vec); - if (c == NULL) { - ret = -1; - goto out; - } - - /* for each expected completion, check it is there */ - for (s = va_arg(ap, const char *); - s != EC_NODE_ENDLIST; - s = va_arg(ap, const char *)) { - struct ec_comp_iter *iter; - const struct ec_comp_item *item; - - if (s == NULL) { - ret = -1; - goto out; - } - - count++; - - /* only check matching completions */ - iter = ec_comp_iter(c, type); - while ((item = ec_comp_iter_next(iter)) != NULL) { - const char *str = ec_comp_item_get_str(item); - if (str != NULL && strcmp(str, s) == 0) - break; - } - - if (item == NULL) { - EC_LOG(EC_LOG_ERR, - "completion <%s> not in list\n", s); - ret = -1; - } - ec_comp_iter_free(iter); - } - - /* check if we have more completions (or less) than expected */ - if (count != ec_comp_count(c, type)) { - EC_LOG(EC_LOG_ERR, - "nb_completion (%d) does not match (%d)\n", - count, ec_comp_count(c, type)); - ec_comp_dump(stdout, c); - ret = -1; - } - -out: - ec_strvec_free(vec); - ec_comp_free(c); - va_end(ap); - return ret; -} - -static int launch_test(const char *name) -{ - struct ec_test *test; - int ret = 0; - unsigned int count = 0; - - TAILQ_FOREACH(test, &test_list, next) { - if (name != NULL && strcmp(name, test->name)) - continue; - - EC_LOG(EC_LOG_INFO, "== starting test %-20s\n", - test->name); - - count++; - if (test->test() == 0) { - EC_LOG(EC_LOG_INFO, - "== test %-20s success\n", - test->name); - } else { - EC_LOG(EC_LOG_INFO, - "== test %-20s failed\n", - test->name); - ret = -1; - } - } - - if (name != NULL && count == 0) { - EC_LOG(EC_LOG_WARNING, - "== test %s not found\n", name); - ret = -1; - } - - return ret; -} - -int ec_test_all(void) -{ - return launch_test(NULL); -} - -int ec_test_one(const char *name) -{ - return launch_test(name); -} diff --git a/lib/ecoli_test.h b/lib/ecoli_test.h deleted file mode 100644 index 94cd0b9..0000000 --- a/lib/ecoli_test.h +++ /dev/null @@ -1,97 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_TEST_ -#define ECOLI_TEST_ - -#include - -#include - -struct ec_node; -enum ec_comp_type; - -#define EC_TEST_REGISTER(t) \ - static void ec_test_init_##t(void); \ - static void __attribute__((constructor, used)) \ - ec_test_init_##t(void) \ - { \ - if (ec_test_register(&t) < 0) \ - fprintf(stderr, "cannot register test %s\n", \ - t.name); \ - } - -/** - * Type of test function. Return 0 on success, -1 on error. - */ -typedef int (ec_test_t)(void); - -TAILQ_HEAD(ec_test_list, ec_test); - -/** - * A structure describing a test case. - */ -struct ec_test { - TAILQ_ENTRY(ec_test) next; /**< Next in list. */ - const char *name; /**< Test name. */ - ec_test_t *test; /**< Test function. */ -}; - -/** - * Register a test case. - * - * @param test - * A pointer to a ec_test structure describing the test - * to be registered. - * @return - * 0 on success, -1 on error (errno is set). - */ -int ec_test_register(struct ec_test *test); - -int ec_test_all(void); -int ec_test_one(const char *name); - -/* expected == -1 means no match */ -int ec_test_check_parse(struct ec_node *node, int expected, ...); - -#define EC_TEST_ERR(fmt, ...) \ - EC_LOG(EC_LOG_ERR, "%s:%d: error: " fmt "\n", \ - __FILE__, __LINE__, ##__VA_ARGS__); \ - -#define EC_TEST_CHECK(cond, fmt, ...) ({ \ - int ret_ = 0; \ - if (!(cond)) { \ - EC_TEST_ERR("(" #cond ") is wrong. " fmt \ - ##__VA_ARGS__); \ - ret_ = -1; \ - } \ - ret_; \ -}) - -/* node, input, [expected1, expected2, ...] */ -#define EC_TEST_CHECK_PARSE(node, args...) ({ \ - int ret_ = ec_test_check_parse(node, args, EC_NODE_ENDLIST); \ - if (ret_) \ - EC_TEST_ERR("parse test failed"); \ - ret_; \ -}) - -int ec_test_check_complete(struct ec_node *node, - enum ec_comp_type type, ...); - -#define EC_TEST_CHECK_COMPLETE(node, args...) ({ \ - int ret_ = ec_test_check_complete(node, EC_COMP_FULL, args); \ - if (ret_) \ - EC_TEST_ERR("complete test failed"); \ - ret_; \ -}) - -#define EC_TEST_CHECK_COMPLETE_PARTIAL(node, args...) ({ \ - int ret_ = ec_test_check_complete(node, EC_COMP_PARTIAL, args); \ - if (ret_) \ - EC_TEST_ERR("complete test failed"); \ - ret_; \ -}) - -#endif diff --git a/lib/ecoli_vec.c b/lib/ecoli_vec.c deleted file mode 100644 index fe8c572..0000000 --- a/lib/ecoli_vec.c +++ /dev/null @@ -1,468 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -EC_LOG_TYPE_REGISTER(vec); - -struct ec_vec { - size_t len; - size_t size; - size_t elt_size; - ec_vec_elt_copy_t copy; - ec_vec_elt_free_t free; - void *vec; -}; - -static void *get_obj(const struct ec_vec *vec, size_t idx) -{ - assert(vec->elt_size != 0); - return (char *)vec->vec + (idx * vec->elt_size); -} - -struct ec_vec * -ec_vec(size_t elt_size, size_t size, ec_vec_elt_copy_t copy, - ec_vec_elt_free_t free) -{ - struct ec_vec *vec; - - if (elt_size == 0) { - errno = EINVAL; - return NULL; - } - - vec = ec_calloc(1, sizeof(*vec)); - if (vec == NULL) - return NULL; - - vec->elt_size = elt_size; - vec->copy = copy; - vec->free = free; - - if (size == 0) - return vec; - - vec->vec = ec_calloc(size, vec->elt_size); - if (vec->vec == NULL) { - ec_free(vec); - return NULL; - } - - return vec; -} - -int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr) -{ - void *new_vec; - - if (vec->len + 1 > vec->size) { - new_vec = ec_realloc(vec->vec, vec->elt_size * (vec->len + 1)); - if (new_vec == NULL) - return -1; - vec->size = vec->len + 1; - vec->vec = new_vec; - } - - memcpy(get_obj(vec, vec->len), ptr, vec->elt_size); - vec->len++; - - return 0; -} - -int ec_vec_add_ptr(struct ec_vec *vec, void *elt) -{ - EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); - - return ec_vec_add_by_ref(vec, &elt); -} - -int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt) -{ - EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); - - return ec_vec_add_by_ref(vec, &elt); -} - -int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt) -{ - EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); - - return ec_vec_add_by_ref(vec, &elt); -} - -int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt) -{ - EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); - - return ec_vec_add_by_ref(vec, &elt); -} - -int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt) -{ - EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); - - return ec_vec_add_by_ref(vec, &elt); -} - -struct ec_vec *ec_vec_ndup(const struct ec_vec *vec, size_t off, - size_t len) -{ - struct ec_vec *copy = NULL; - size_t i, veclen; - - veclen = ec_vec_len(vec); - if (off + len > veclen) - return NULL; - - copy = ec_vec(vec->elt_size, len, vec->copy, vec->free); - if (copy == NULL) - goto fail; - - if (len == 0) - return copy; - - for (i = 0; i < len; i++) { - if (vec->copy) - vec->copy(get_obj(copy, i), get_obj(vec, i + off)); - else - memcpy(get_obj(copy, i), get_obj(vec, i + off), - vec->elt_size); - } - copy->len = len; - - return copy; - -fail: - ec_vec_free(copy); - return NULL; -} - -size_t ec_vec_len(const struct ec_vec *vec) -{ - if (vec == NULL) - return 0; - - return vec->len; -} - -struct ec_vec *ec_vec_dup(const struct ec_vec *vec) -{ - return ec_vec_ndup(vec, 0, ec_vec_len(vec)); -} - -void ec_vec_free(struct ec_vec *vec) -{ - size_t i; - - if (vec == NULL) - return; - - for (i = 0; i < ec_vec_len(vec); i++) { - if (vec->free) - vec->free(get_obj(vec, i)); - } - - ec_free(vec->vec); - ec_free(vec); -} - -int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx) -{ - if (vec == NULL || idx >= vec->len) { - errno = EINVAL; - return -1; - } - - memcpy(ptr, get_obj(vec, idx), vec->elt_size); - - return 0; -} - -static void str_free(void *elt) -{ - char **s = elt; - - ec_free(*s); -} - -#define GOTO_FAIL do { \ - EC_LOG(EC_LOG_ERR, "%s:%d: test failed\n", \ - __FILE__, __LINE__); \ - goto fail; \ - } while(0) - -/* LCOV_EXCL_START */ -static int ec_vec_testcase(void) -{ - struct ec_vec *vec = NULL; - struct ec_vec *vec2 = NULL; - uint8_t val8; - uint16_t val16; - uint32_t val32; - uint64_t val64; - void *valp; - char *vals; - - /* uint8_t vector */ - vec = ec_vec(sizeof(val8), 0, NULL, NULL); - if (vec == NULL) - GOTO_FAIL; - - if (ec_vec_add_u8(vec, 0) < 0) - GOTO_FAIL; - if (ec_vec_add_u8(vec, 1) < 0) - GOTO_FAIL; - if (ec_vec_add_u8(vec, 2) < 0) - GOTO_FAIL; - /* should fail */ - if (ec_vec_add_u16(vec, 3) == 0) - GOTO_FAIL; - if (ec_vec_add_u32(vec, 3) == 0) - GOTO_FAIL; - if (ec_vec_add_u64(vec, 3) == 0) - GOTO_FAIL; - if (ec_vec_add_ptr(vec, (void *)3) == 0) - GOTO_FAIL; - - if (ec_vec_get(&val8, vec, 0) < 0) - GOTO_FAIL; - if (val8 != 0) - GOTO_FAIL; - if (ec_vec_get(&val8, vec, 1) < 0) - GOTO_FAIL; - if (val8 != 1) - GOTO_FAIL; - if (ec_vec_get(&val8, vec, 2) < 0) - GOTO_FAIL; - if (val8 != 2) - GOTO_FAIL; - - /* duplicate the vector */ - vec2 = ec_vec_dup(vec); - if (vec2 == NULL) - GOTO_FAIL; - if (ec_vec_get(&val8, vec2, 0) < 0) - GOTO_FAIL; - if (val8 != 0) - GOTO_FAIL; - if (ec_vec_get(&val8, vec2, 1) < 0) - GOTO_FAIL; - if (val8 != 1) - GOTO_FAIL; - if (ec_vec_get(&val8, vec2, 2) < 0) - GOTO_FAIL; - if (val8 != 2) - GOTO_FAIL; - - ec_vec_free(vec2); - vec2 = NULL; - - /* dup at offset 1 */ - vec2 = ec_vec_ndup(vec, 1, 2); - if (vec2 == NULL) - GOTO_FAIL; - if (ec_vec_get(&val8, vec2, 0) < 0) - GOTO_FAIL; - if (val8 != 1) - GOTO_FAIL; - if (ec_vec_get(&val8, vec2, 1) < 0) - GOTO_FAIL; - if (val8 != 2) - GOTO_FAIL; - - ec_vec_free(vec2); - vec2 = NULL; - - /* len = 0, duplicate is empty */ - vec2 = ec_vec_ndup(vec, 2, 0); - if (vec2 == NULL) - GOTO_FAIL; - if (ec_vec_get(&val8, vec2, 0) == 0) - GOTO_FAIL; - - ec_vec_free(vec2); - vec2 = NULL; - - /* bad dup args */ - vec2 = ec_vec_ndup(vec, 10, 1); - if (vec2 != NULL) - GOTO_FAIL; - - ec_vec_free(vec); - vec = NULL; - - /* uint16_t vector */ - vec = ec_vec(sizeof(val16), 0, NULL, NULL); - if (vec == NULL) - GOTO_FAIL; - - if (ec_vec_add_u16(vec, 0) < 0) - GOTO_FAIL; - if (ec_vec_add_u16(vec, 1) < 0) - GOTO_FAIL; - if (ec_vec_add_u16(vec, 2) < 0) - GOTO_FAIL; - /* should fail */ - if (ec_vec_add_u8(vec, 3) == 0) - GOTO_FAIL; - - if (ec_vec_get(&val16, vec, 0) < 0) - GOTO_FAIL; - if (val16 != 0) - GOTO_FAIL; - if (ec_vec_get(&val16, vec, 1) < 0) - GOTO_FAIL; - if (val16 != 1) - GOTO_FAIL; - if (ec_vec_get(&val16, vec, 2) < 0) - GOTO_FAIL; - if (val16 != 2) - GOTO_FAIL; - - ec_vec_free(vec); - vec = NULL; - - /* uint32_t vector */ - vec = ec_vec(sizeof(val32), 0, NULL, NULL); - if (vec == NULL) - GOTO_FAIL; - - if (ec_vec_add_u32(vec, 0) < 0) - GOTO_FAIL; - if (ec_vec_add_u32(vec, 1) < 0) - GOTO_FAIL; - if (ec_vec_add_u32(vec, 2) < 0) - GOTO_FAIL; - - if (ec_vec_get(&val32, vec, 0) < 0) - GOTO_FAIL; - if (val32 != 0) - GOTO_FAIL; - if (ec_vec_get(&val32, vec, 1) < 0) - GOTO_FAIL; - if (val32 != 1) - GOTO_FAIL; - if (ec_vec_get(&val32, vec, 2) < 0) - GOTO_FAIL; - if (val32 != 2) - GOTO_FAIL; - - ec_vec_free(vec); - vec = NULL; - - /* uint64_t vector */ - vec = ec_vec(sizeof(val64), 0, NULL, NULL); - if (vec == NULL) - GOTO_FAIL; - - if (ec_vec_add_u64(vec, 0) < 0) - GOTO_FAIL; - if (ec_vec_add_u64(vec, 1) < 0) - GOTO_FAIL; - if (ec_vec_add_u64(vec, 2) < 0) - GOTO_FAIL; - - if (ec_vec_get(&val64, vec, 0) < 0) - GOTO_FAIL; - if (val64 != 0) - GOTO_FAIL; - if (ec_vec_get(&val64, vec, 1) < 0) - GOTO_FAIL; - if (val64 != 1) - GOTO_FAIL; - if (ec_vec_get(&val64, vec, 2) < 0) - GOTO_FAIL; - if (val64 != 2) - GOTO_FAIL; - - ec_vec_free(vec); - vec = NULL; - - /* ptr vector */ - vec = ec_vec(sizeof(valp), 0, NULL, NULL); - if (vec == NULL) - GOTO_FAIL; - - if (ec_vec_add_ptr(vec, (void *)0) < 0) - GOTO_FAIL; - if (ec_vec_add_ptr(vec, (void *)1) < 0) - GOTO_FAIL; - if (ec_vec_add_ptr(vec, (void *)2) < 0) - GOTO_FAIL; - - if (ec_vec_get(&valp, vec, 0) < 0) - GOTO_FAIL; - if (valp != (void *)0) - GOTO_FAIL; - if (ec_vec_get(&valp, vec, 1) < 0) - GOTO_FAIL; - if (valp != (void *)1) - GOTO_FAIL; - if (ec_vec_get(&valp, vec, 2) < 0) - GOTO_FAIL; - if (valp != (void *)2) - GOTO_FAIL; - - ec_vec_free(vec); - vec = NULL; - - /* string vector */ - vec = ec_vec(sizeof(valp), 0, NULL, str_free); - if (vec == NULL) - GOTO_FAIL; - - if (ec_vec_add_ptr(vec, ec_strdup("0")) < 0) - GOTO_FAIL; - if (ec_vec_add_ptr(vec, ec_strdup("1")) < 0) - GOTO_FAIL; - if (ec_vec_add_ptr(vec, ec_strdup("2")) < 0) - GOTO_FAIL; - - if (ec_vec_get(&vals, vec, 0) < 0) - GOTO_FAIL; - if (vals == NULL || strcmp(vals, "0")) - GOTO_FAIL; - if (ec_vec_get(&vals, vec, 1) < 0) - GOTO_FAIL; - if (vals == NULL || strcmp(vals, "1")) - GOTO_FAIL; - if (ec_vec_get(&vals, vec, 2) < 0) - GOTO_FAIL; - if (vals == NULL || strcmp(vals, "2")) - GOTO_FAIL; - - ec_vec_free(vec); - vec = NULL; - - /* invalid args */ - vec = ec_vec(0, 0, NULL, NULL); - if (vec != NULL) - GOTO_FAIL; - - return 0; - -fail: - ec_vec_free(vec); - ec_vec_free(vec2); - return -1; -} -/* LCOV_EXCL_STOP */ - -static struct ec_test ec_vec_test = { - .name = "vec", - .test = ec_vec_testcase, -}; - -EC_TEST_REGISTER(ec_vec_test); diff --git a/lib/ecoli_vec.h b/lib/ecoli_vec.h deleted file mode 100644 index 5fdaa99..0000000 --- a/lib/ecoli_vec.h +++ /dev/null @@ -1,47 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -/** - * Vectors of objects. - * - * The ec_vec API provide helpers to manipulate vectors of objects - * of any kind. - */ - -#ifndef ECOLI_VEC_ -#define ECOLI_VEC_ - -#include -#include -#include - -/* if NULL, default does nothing */ -typedef void (*ec_vec_elt_free_t)(void *ptr); - -/* if NULL, default is: - * memcpy(dst, src, vec->elt_size) - */ -typedef void (*ec_vec_elt_copy_t)(void *dst, void *src); - -struct ec_vec *ec_vec(size_t elt_size, size_t size, - ec_vec_elt_copy_t copy, ec_vec_elt_free_t free); -int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr); - -int ec_vec_add_ptr(struct ec_vec *vec, void *elt); -int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt); -int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt); -int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt); -int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt); - -int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx); - -struct ec_vec *ec_vec_dup(const struct ec_vec *vec); -struct ec_vec *ec_vec_ndup(const struct ec_vec *vec, - size_t off, size_t len); -void ec_vec_free(struct ec_vec *vec); - -__attribute__((pure)) -size_t ec_vec_len(const struct ec_vec *vec); - -#endif diff --git a/lib/main-readline.c b/lib/main-readline.c deleted file mode 100644 index 2c81c53..0000000 --- a/lib/main-readline.c +++ /dev/null @@ -1,361 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#define _GNU_SOURCE /* for asprintf */ -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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] = ""; - - while ((item = ec_comp_iter_next(iter)) != NULL) { - char **tmp; - - /* keep one help per group, skip other items */ - grp = ec_comp_item_get_grp(item); - if (grp == prev_grp) - continue; - - prev_grp = grp; - - tmp = realloc(helps, (count + match + 2) * sizeof(char *)); - if (tmp == NULL) - goto fail; - helps = tmp; - helps[count + match + 1] = get_node_help(item); - count++; - } - - ec_comp_iter_free(iter); - ec_comp_free(c); - /* ensure not more than 1 entry per line */ - rl_get_screen_size(NULL, &cols); - rl_display_match_list(helps, count + match, cols); - rl_forced_update_display(); - - return 0; - -fail: - ec_comp_iter_free(iter); - ec_parse_free(p); - free(line); - ec_comp_free(c); - if (helps != NULL) { - while (count--) - free(helps[count + match + 1]); - } - free(helps); - - return 1; -} - -static int create_commands(void) -{ - struct ec_node *cmdlist = NULL, *cmd = NULL; - - cmdlist = ec_node("or", EC_NO_ID); - if (cmdlist == NULL) - goto fail; - - - cmd = EC_NODE_SEQ(EC_NO_ID, - ec_node_str(EC_NO_ID, "hello"), - EC_NODE_OR("name", - ec_node_str("john", "john"), - ec_node_str(EC_NO_ID, "johnny"), - ec_node_str(EC_NO_ID, "mike") - ), - ec_node_option(EC_NO_ID, ec_node_int("int", 0, 10, 10)) - ); - if (cmd == NULL) - goto fail; - ec_keyval_set(ec_node_attrs(cmd), "help", - "say hello to someone several times", NULL); - ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "john")), - "help", "specific help for john", NULL); - ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), - "help", "the name of the person", NULL); - ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "int")), - "help", "an integer (0-10)", NULL); - if (ec_node_or_add(cmdlist, cmd) < 0) - goto fail; - - - cmd = EC_NODE_CMD(EC_NO_ID, "good morning name [count]", - EC_NODE_CMD("name", "bob|bobby|michael"), - ec_node_int("count", 0, 10, 10)); - if (cmd == NULL) - goto fail; - ec_keyval_set(ec_node_attrs(cmd), "help", - "say good morning to someone several times", NULL); - ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), "help", - "the person to greet", NULL); - ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "count")), "help", - "how many times to greet (0-10)", NULL); - if (ec_node_or_add(cmdlist, cmd) < 0) - goto fail; - - - cmd = EC_NODE_CMD(EC_NO_ID, - "buy potatoes,carrots,pumpkins"); - if (cmd == NULL) - goto fail; - ec_keyval_set(ec_node_attrs(cmd), "help", - "buy some vegetables", NULL); - if (ec_node_or_add(cmdlist, cmd) < 0) - goto fail; - - - cmd = EC_NODE_CMD(EC_NO_ID, "eat vegetables", - ec_node_many("vegetables", - EC_NODE_OR(EC_NO_ID, - ec_node_str(EC_NO_ID, "potatoes"), - ec_node_once(EC_NO_ID, - ec_node_str(EC_NO_ID, "carrots")), - ec_node_once(EC_NO_ID, - ec_node_str(EC_NO_ID, "pumpkins"))), - 1, 0)); - if (cmd == NULL) - goto fail; - ec_keyval_set(ec_node_attrs(cmd), "help", - "eat vegetables (take some more potatoes)", NULL); - if (ec_node_or_add(cmdlist, cmd) < 0) - goto fail; - - - cmd = EC_NODE_SEQ(EC_NO_ID, - ec_node_str(EC_NO_ID, "bye") - ); - ec_keyval_set(ec_node_attrs(cmd), "help", "say bye", NULL); - if (ec_node_or_add(cmdlist, cmd) < 0) - goto fail; - - - cmd = EC_NODE_SEQ(EC_NO_ID, - ec_node_str(EC_NO_ID, "load"), - ec_node("file", EC_NO_ID) - ); - ec_keyval_set(ec_node_attrs(cmd), "help", "load a file", NULL); - if (ec_node_or_add(cmdlist, cmd) < 0) - goto fail; - - - commands = ec_node_sh_lex(EC_NO_ID, cmdlist); - if (commands == NULL) - goto fail; - - return 0; - - fail: - fprintf(stderr, "cannot initialize nodes\n"); - ec_node_free(cmdlist); - return -1; -} - -int main(void) -{ - struct ec_parse *p; - char *line; - - if (ec_init() < 0) { - fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno)); - return 1; - } - - if (create_commands() < 0) - return 1; - - rl_bind_key('?', show_help); - rl_attempted_completion_function = my_attempted_completion; - - while (1) { - line = readline("> "); - if (line == NULL) - break; - - p = ec_node_parse(commands, line); - ec_parse_dump(stdout, p); - add_history(line); - ec_parse_free(p); - } - - - ec_node_free(commands); - return 0; - -} diff --git a/lib/main.c b/lib/main.c deleted file mode 100644 index 1b2b7ef..0000000 --- a/lib/main.c +++ /dev/null @@ -1,407 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -/* 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 \n" - " --"EC_OPT_LOG_LEVEL"=\n" - " Set log level (0 = no log, 7 = verbose).\n" - " -r \n" - " --"EC_OPT_RANDOM_ALLOC_FAIL"=\n" - " Cause malloc to fail randomly. This helps to debug\n" - " leaks or crashes in error cases. The probability is\n" - " between 0 and 100.\n" - " -s \n" - " --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 \n"); - - return -1; -} - -static int debug_log(int type, unsigned int level, void *opaque, - const char *str) -{ - (void)type; - (void)opaque; - - if (level > (unsigned int)log_level) - return 0; - - if (printf("%s", str) < 0) - return -1; - - return 0; -} - -int main(int argc, char **argv) -{ - int i, ret = 0, leaks; - - ret = parse_args(argc, argv); - if (ret < 0) - return 1; - - argc -= ret; - argv += ret; - - srandom(seed); - - /* register a new malloc to track memleaks */ - TAILQ_INIT(&debug_alloc_hdr_list); - if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) { - EC_LOG(EC_LOG_ERR, "cannot register new malloc\n"); - return 1; - } - - if (ec_init() < 0) { - fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno)); - return 1; - } - ec_log_fct_register(debug_log, NULL); - - ret = 0; - if (argc <= 1) { - ret = ec_test_all(); - } else { - for (i = 1; i < argc; i++) - ret |= ec_test_one(argv[i]); - } - - leaks = debug_alloc_dump_leaks(); - - if (alloc_fail_proba == 0 && ret != 0) { - printf("tests failed\n"); - return 1; - } else if (alloc_fail_proba != 0 && leaks != 0) { - printf("tests failed (memory leak)\n"); - return 1; - } - - printf("\ntests ok\n"); - - return 0; -} -/* LCOV_EXCL_STOP */ diff --git a/lib/parse-yaml.c b/lib/parse-yaml.c deleted file mode 100644 index 48bcdc7..0000000 --- a/lib/parse-yaml.c +++ /dev/null @@ -1,552 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -/* associate a yaml node to a ecoli node */ -struct pair { - const yaml_node_t *ynode; - struct ec_node *enode; -}; - -/* store the ecoli node tree and the associations yaml_node <-> ec_node */ -struct enode_tree { - struct ec_node *root; - struct pair *table; - size_t table_len; -}; - -static struct ec_node * -parse_ec_node(struct enode_tree *tree, - const yaml_document_t *document, const yaml_node_t *ynode); - -static struct ec_config * -parse_ec_config_list(struct enode_tree *tree, - const struct ec_config_schema *schema, - const yaml_document_t *document, const yaml_node_t *ynode); - -static struct ec_config * -parse_ec_config_dict(struct enode_tree *tree, - const struct ec_config_schema *schema, - const yaml_document_t *document, const yaml_node_t *ynode); - -/* XXX to utils.c ? */ -static int -parse_llint(const char *str, int64_t *val) -{ - char *endptr; - int save_errno = errno; - - errno = 0; - *val = strtoll(str, &endptr, 0); - - if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) || - (errno != 0 && *val == 0)) - return -1; - - if (*endptr != 0) { - errno = EINVAL; - return -1; - } - - errno = save_errno; - return 0; -} - -static int -parse_ullint(const char *str, uint64_t *val) -{ - char *endptr; - int save_errno = errno; - - /* since a negative input is silently converted to a positive - * one by strtoull(), first check that it is positive */ - if (strchr(str, '-')) - return -1; - - errno = 0; - *val = strtoull(str, &endptr, 0); - - if ((errno == ERANGE && *val == ULLONG_MAX) || - (errno != 0 && *val == 0)) - return -1; - - if (*endptr != 0) - return -1; - - errno = save_errno; - return 0; -} - -static int -parse_bool(const char *str, bool *val) -{ - if (!strcasecmp(str, "true")) { - *val = true; - return 0; - } else if (!strcasecmp(str, "false")) { - *val = false; - return 0; - } - errno = EINVAL; - return -1; -} - -static int -add_in_table(struct enode_tree *tree, const yaml_node_t *ynode, - struct ec_node *enode) -{ - struct pair *table = NULL; - - table = realloc(tree->table, (tree->table_len + 1) * sizeof(*table)); - if (table == NULL) - return -1; - - ec_node_clone(enode); - table[tree->table_len].ynode = ynode; - table[tree->table_len].enode = enode; - tree->table = table; - tree->table_len++; - - return 0; -} - -static void -free_tree(struct enode_tree *tree) -{ - size_t i; - - if (tree->root != NULL) - ec_node_free(tree->root); - for (i = 0; i < tree->table_len; i++) - ec_node_free(tree->table[i].enode); - free(tree->table); -} - -static struct ec_config * -parse_ec_config(struct enode_tree *tree, - const struct ec_config_schema *schema_elt, - const yaml_document_t *document, const yaml_node_t *ynode) -{ - const struct ec_config_schema *subschema; - struct ec_config *config = NULL; - struct ec_node *enode = NULL; - enum ec_config_type type; - const char *value_str; - uint64_t u64; - int64_t i64; - bool boolean; - - type = ec_config_schema_type(schema_elt); - - switch (type) { - case EC_CONFIG_TYPE_BOOL: - if (ynode->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Boolean should be scalar\n"); - goto fail; - } - value_str = (const char *)ynode->data.scalar.value; - if (parse_bool(value_str, &boolean) < 0) { - fprintf(stderr, "Failed to parse boolean\n"); - goto fail; - } - config = ec_config_bool(boolean); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_INT64: - if (ynode->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Int64 should be scalar\n"); - goto fail; - } - value_str = (const char *)ynode->data.scalar.value; - if (parse_llint(value_str, &i64) < 0) { - fprintf(stderr, "Failed to parse i64\n"); - goto fail; - } - config = ec_config_i64(i64); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_UINT64: - if (ynode->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Uint64 should be scalar\n"); - goto fail; - } - value_str = (const char *)ynode->data.scalar.value; - if (parse_ullint(value_str, &u64) < 0) { - fprintf(stderr, "Failed to parse u64\n"); - goto fail; - } - config = ec_config_u64(u64); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_STRING: - if (ynode->type != YAML_SCALAR_NODE) { - fprintf(stderr, "String should be scalar\n"); - goto fail; - } - value_str = (const char *)ynode->data.scalar.value; - config = ec_config_string(value_str); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_NODE: - enode = parse_ec_node(tree, document, ynode); - if (enode == NULL) - goto fail; - config = ec_config_node(enode); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_LIST: - subschema = ec_config_schema_sub(schema_elt); - if (subschema == NULL) { - fprintf(stderr, "List has no subschema\n"); - goto fail; - } - config = parse_ec_config_list(tree, subschema, document, ynode); - if (config == NULL) - goto fail; - break; - case EC_CONFIG_TYPE_DICT: - subschema = ec_config_schema_sub(schema_elt); - if (subschema == NULL) { - fprintf(stderr, "Dict has no subschema\n"); - goto fail; - } - config = parse_ec_config_dict(tree, subschema, document, ynode); - if (config == NULL) - goto fail; - break; - default: - fprintf(stderr, "Invalid config type %d\n", type); - goto fail; - } - - return config; - -fail: - ec_node_free(enode); - ec_config_free(config); - return NULL; -} - -static struct ec_config * -parse_ec_config_list(struct enode_tree *tree, - const struct ec_config_schema *schema, - const yaml_document_t *document, const yaml_node_t *ynode) -{ - struct ec_config *config = NULL, *subconfig = NULL; - const yaml_node_item_t *item; - const yaml_node_t *value; - - (void)tree; - (void)schema; - (void)document; - - if (ynode->type != YAML_SEQUENCE_NODE) { - fprintf(stderr, "Ecoli list config should be a yaml sequence\n"); - goto fail; - } - - config = ec_config_list(); - if (config == NULL) { - fprintf(stderr, "Failed to allocate config\n"); - goto fail; - } - - for (item = ynode->data.sequence.items.start; - item < ynode->data.sequence.items.top; item++) { - value = document->nodes.start + (*item) - 1; // XXX -1 ? - subconfig = parse_ec_config(tree, schema, document, value); - if (subconfig == NULL) - goto fail; - if (ec_config_list_add(config, subconfig) < 0) { - fprintf(stderr, "Failed to add list entry\n"); - goto fail; - } - } - - return config; - -fail: - ec_config_free(config); - return NULL; -} - -static struct ec_config * -parse_ec_config_dict(struct enode_tree *tree, - const struct ec_config_schema *schema, - const yaml_document_t *document, const yaml_node_t *ynode) -{ - const struct ec_config_schema *schema_elt; - struct ec_config *config = NULL, *subconfig = NULL; - const yaml_node_t *key, *value; - const yaml_node_pair_t *pair; - const char *key_str; - - if (ynode->type != YAML_MAPPING_NODE) { - fprintf(stderr, "Ecoli config should be a yaml mapping node\n"); - goto fail; - } - - config = ec_config_dict(); - if (config == NULL) { - fprintf(stderr, "Failed to allocate config\n"); - goto fail; - } - - for (pair = ynode->data.mapping.pairs.start; - pair < ynode->data.mapping.pairs.top; pair++) { - key = document->nodes.start + pair->key - 1; // XXX -1 ? - value = document->nodes.start + pair->value - 1; - key_str = (const char *)key->data.scalar.value; - - if (ec_config_key_is_reserved(key_str)) - continue; - schema_elt = ec_config_schema_lookup(schema, key_str); - if (schema_elt == NULL) { - fprintf(stderr, "No such config %s\n", key_str); - goto fail; - } - subconfig = parse_ec_config(tree, schema_elt, document, value); - if (subconfig == NULL) - goto fail; - if (ec_config_dict_set(config, key_str, subconfig) < 0) { - fprintf(stderr, "Failed to set dict entry\n"); - goto fail; - } - } - - return config; - -fail: - ec_config_free(config); - return NULL; -} - -static struct ec_node * -parse_ec_node(struct enode_tree *tree, - const yaml_document_t *document, const yaml_node_t *ynode) -{ - const struct ec_config_schema *schema; - const struct ec_node_type *type = NULL; - const char *id = NULL, *help = NULL; - struct ec_config *config = NULL; - const yaml_node_t *attrs = NULL; - const yaml_node_t *key, *value; - const yaml_node_pair_t *pair; - const char *key_str, *value_str; - struct ec_node *enode = NULL; - - if (ynode->type != YAML_MAPPING_NODE) { - fprintf(stderr, "Ecoli node should be a yaml mapping node\n"); - goto fail; - } - - for (pair = ynode->data.mapping.pairs.start; - pair < ynode->data.mapping.pairs.top; pair++) { - key = document->nodes.start + pair->key - 1; // XXX -1 ? - value = document->nodes.start + pair->value - 1; - key_str = (const char *)key->data.scalar.value; - value_str = (const char *)value->data.scalar.value; - - if (!strcmp(key_str, "type")) { - if (type != NULL) { - fprintf(stderr, "Duplicate type\n"); - goto fail; - } - if (value->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Type must be a string\n"); - goto fail; - } - type = ec_node_type_lookup(value_str); - if (type == NULL) { - fprintf(stderr, "Cannot find type %s\n", - value_str); - goto fail; - } - } else if (!strcmp(key_str, "attrs")) { - if (attrs != NULL) { - fprintf(stderr, "Duplicate attrs\n"); - goto fail; - } - if (value->type != YAML_MAPPING_NODE) { - fprintf(stderr, "Attrs must be a maping\n"); - goto fail; - } - attrs = value; - } else if (!strcmp(key_str, "id")) { - if (id != NULL) { - fprintf(stderr, "Duplicate id\n"); - goto fail; - } - if (value->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Id must be a scalar\n"); - goto fail; - } - id = value_str; - } else if (!strcmp(key_str, "help")) { - if (help != NULL) { - fprintf(stderr, "Duplicate help\n"); - goto fail; - } - if (value->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Help must be a scalar\n"); - goto fail; - } - help = value_str; - } - } - - /* create the ecoli node */ - if (id == NULL) - id = EC_NO_ID; - enode = ec_node_from_type(type, id); - if (enode == NULL) { - fprintf(stderr, "Cannot create ecoli node\n"); - goto fail; - } - if (add_in_table(tree, ynode, enode) < 0) { - fprintf(stderr, "Cannot add node in table\n"); - goto fail; - } - if (tree->root == NULL) { - ec_node_clone(enode); - tree->root = enode; - } - - /* create its config */ - schema = ec_node_type_schema(type); - if (schema == NULL) { - fprintf(stderr, "No configuration schema for type %s\n", - ec_node_type_name(type)); - goto fail; - } - - config = parse_ec_config_dict(tree, schema, document, ynode); - if (config == NULL) - goto fail; - - if (ec_node_set_config(enode, config) < 0) { - fprintf(stderr, "Failed to set config\n"); - goto fail; - } - - /* add attributes (all as string) */ - //XXX - - return enode; - -fail: - ec_node_free(enode); - ec_config_free(config); - return NULL; -} - -static int -parse_document(struct enode_tree *tree, const yaml_document_t *document) -{ - yaml_node_t *node; - - node = document->nodes.start; - if (parse_ec_node(tree, document, node) == NULL) - return -1; - - return 0; -} - -static int parse_file(struct enode_tree *tree, const char *filename) -{ - FILE *file; - yaml_parser_t parser; - yaml_document_t document; - - file = fopen(filename, "rb"); - if (file == NULL) { - fprintf(stderr, "Failed to open file %s\n", filename); - goto fail_no_doc; - } - - if (yaml_parser_initialize(&parser) == 0) { - fprintf(stderr, "Failed to initialize yaml parser\n"); - goto fail_no_doc; - } - - yaml_parser_set_input_file(&parser, file); - - if (yaml_parser_load(&parser, &document) == 0) { - fprintf(stderr, "Failed to load yaml document\n"); - goto fail_no_doc; - } - - if (yaml_document_get_root_node(&document) == NULL) { - fprintf(stderr, "Incomplete document\n"); //XXX check err - goto fail; - } - - if (parse_document(tree, &document) < 0) { - fprintf(stderr, "Failed to parse document\n"); - goto fail; - } - - yaml_document_delete(&document); - yaml_parser_delete(&parser); - fclose(file); - - return 0; - -fail: - yaml_document_delete(&document); -fail_no_doc: - yaml_parser_delete(&parser); - if (file != NULL) - fclose(file); - - return -1; -} - -int -main(int argc, char *argv[]) -{ - struct enode_tree tree; - - memset(&tree, 0, sizeof(tree)); - - if (argc != 2) { - fprintf(stderr, "Invalid args\n"); - goto fail; - } - if (parse_file(&tree, argv[1]) < 0) { - fprintf(stderr, "Failed to parse file\n"); - goto fail; - } - printf("root=%p len=%zd\n", tree.root, tree.table_len); - ec_node_dump(stdout, tree.root); - free_tree(&tree); - - return 0; - -fail: - free_tree(&tree); - return 1; -} diff --git a/lib/test.sh b/lib/test.sh deleted file mode 100755 index 83a7538..0000000 --- a/lib/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2016, Olivier MATZ - -set -e - -SEED=100 -while [ ${SEED} -gt 0 ]; do - CMD="./build/test --random-alloc-fail=1 --seed=${SEED} $*" - ${CMD} --log-level=0 || ( - echo "=== test failed, replay seed=${SEED} with logs ===" && - ${CMD} --log-level=6 || - echo "=== test failed: ${CMD}" && - false - ) - - SEED=$((SEED-1)) && continue -done diff --git a/lib/todo.txt b/lib/todo.txt deleted file mode 100644 index 4e5d94f..0000000 --- a/lib/todo.txt +++ /dev/null @@ -1,421 +0,0 @@ -tk_cmd -====== - -X evaluate expression tree in ec_tk_expr -X cmd token -- example -X tk_re - -cleanup / rework -================ - -X ec_completed_item_update() -X ec_completed_item_set_display_value() -X add_no_match -X add_partial_match -- check XXX in code -X properly manage quotes in shlex -X remove the _new() functions -X iterate children nodes without chaining them -- add a node vector type: will be used in several nodes (ex: or, seq, ...) -- check allocation model everywhere -- checkpatch? -- use linux style (update .emacs) -- better logs -- check return values (-1 or NULL) + use errno -- check missing static / const -- license: SPDX -- check all completion nodes -X split ecoli_tk.h -- size_t or unsigned int? -X rename: - X ec_tk -> ec_node - X ec_parsed_tk -> ec_parsed - X ec_completed_tk -> ec_completed - X tk, gen_tk, token, ... -> node - X tokens -> input_str / input_strvec ? -X save node path in completion to fix help string -- code coverage -- try to hide structures -- anything better than weakref? -- add ec_node_defaults.[ch] providing usual implementations of node methods -X use vec for strvec -- ELOOP in case of loop -- remove weakref? -- sh_lex to provide offsets in attributes -- accessors for all structs - -dependencies -============ - -X pass the current parsed state when parsing/completing -X new node "once" -- new node "condition" - -logs -==== - -X register log types - -yaml -==== - -X register nodes by name -- interface to add attributes: all nodes must be configurable through a - generic api - - attr string - - attr string list - - attr node - - attr node list - - attr int - -- yaml interface to create nodes -- example - -examples -======== - -- example which parses arguments (argc/argv) -- example that acts as bash completion (ip link ?) -- calculator example (var assignation, expression evaluation) -- example with libedit -- mini script language -- configuration file -- mini shell: cd, ls, cat, stat -- mini network console based on ip - -doc -=== - -- overview -- add api doc in .h -- generate automatic api doc -- architecture -- coding rules, process -- each node -- allocation model -- say that it stops at first match (no ambigous support) -- say that completion must be exhaustive - -build framework -=============== - -- .map files for API -- split libs, tests and examples -- add make help -- add make config -- -fvisibility= - -tests -===== - -- complete automatic tests with "make test" - -new nodes -========= - -- regexp -- node which always matches -- file + partial completion -- ether, ip, network -- fusion node: need to match several children, same for completion -- float -- not - -encoding -======== - -- support utf-8 and other encodings -- example -- documentation - -netconf example -=============== - -- demonstration example that parses yang file and generate cli - - - ------------------------ - -readline: - -[tab] list possible completions (matches/partial only) -[?] list what is expected, example: - -"command [foo] toto|titi|" - -help("command f") -> - foo (help of foo) - toto (help of toto) - titi (help of titi) - (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 -#include - - -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 malloc_seq >= - -alternative - -watch malloc_seq -condition malloc_seq == -run -c - - ---------------- - - diff --git a/libecoli/ecoli_assert.c b/libecoli/ecoli_assert.c new file mode 100644 index 0000000..a36d6f6 --- /dev/null +++ b/libecoli/ecoli_assert.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include + +#include + +void __ec_assert_print(bool expr, const char *expr_str, const char *format, ...) +{ + va_list ap; + + if (expr) + return; + + /* LCOV_EXCL_START */ + va_start(ap, format); + fprintf(stderr, "assertion failed: '%s' is false\n", expr_str); + vfprintf(stderr, format, ap); + va_end(ap); + abort(); + /* LCOV_EXCL_END */ +} diff --git a/libecoli/ecoli_assert.h b/libecoli/ecoli_assert.h new file mode 100644 index 0000000..fcd2186 --- /dev/null +++ b/libecoli/ecoli_assert.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 + +/** + * Abort if the condition is false. + * + * If expression is false this macro will prints an error message to + * standard error and terminates the program by calling abort(3). + * + * @param expr + * The expression to be checked. + * @param args + * The format string, optionally followed by other arguments. + */ +#define ec_assert_print(expr, args...) \ + __ec_assert_print(expr, #expr, args) + +/* internal */ +void __ec_assert_print(bool expr, const char *expr_str, + const char *format, ...); + +/** + * Check a condition or return. + * + * If the condition is true, do nothing. If it is false, set + * errno and return the specified value. + * + * @param cond + * The condition to test. + * @param ret + * The value to return. + * @param err + * The errno to set. + */ +#define EC_CHECK_ARG(cond, ret, err) do { \ + if (!(cond)) { \ + errno = err; \ + return ret; \ + } \ + } while(0) + +#endif diff --git a/libecoli/ecoli_complete.c b/libecoli/ecoli_complete.c new file mode 100644 index 0000000..7ad846c --- /dev/null +++ b/libecoli/ecoli_complete.c @@ -0,0 +1,765 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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="), "bad dump\n"); + testres |= EC_TEST_CHECK( + strstr(buf, "comp="), "bad dump\n"); + free(buf); + buf = NULL; + + iter = ec_comp_iter(c, EC_COMP_ALL); + item = ec_comp_iter_next(iter); + if (item == NULL) + goto fail; + + testres |= EC_TEST_CHECK( + !strcmp(ec_comp_item_get_display(item), "xx"), + "bad item display\n"); + testres |= EC_TEST_CHECK( + ec_comp_item_get_type(item) == EC_COMP_FULL, + "bad item type\n"); + testres |= EC_TEST_CHECK( + !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_x"), + "bad item node\n"); + + item = ec_comp_iter_next(iter); + if (item == NULL) + goto fail; + + testres |= EC_TEST_CHECK( + !strcmp(ec_comp_item_get_display(item), "yy"), + "bad item display\n"); + testres |= EC_TEST_CHECK( + ec_comp_item_get_type(item) == EC_COMP_FULL, + "bad item type\n"); + testres |= EC_TEST_CHECK( + !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_y"), + "bad item node\n"); + + item = ec_comp_iter_next(iter); + testres |= EC_TEST_CHECK(item == NULL, "should be the last item\n"); + + ec_comp_iter_free(iter); + ec_comp_free(c); + ec_node_free(node); + + return testres; + +fail: + ec_comp_iter_free(iter); + ec_comp_free(c); + ec_node_free(node); + if (f != NULL) + fclose(f); + free(buf); + + return -1; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_comp_test = { + .name = "comp", + .test = ec_comp_testcase, +}; + +EC_TEST_REGISTER(ec_comp_test); diff --git a/libecoli/ecoli_complete.h b/libecoli/ecoli_complete.h new file mode 100644 index 0000000..1ed67f0 --- /dev/null +++ b/libecoli/ecoli_complete.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 +#include +#include + +struct ec_node; + +enum ec_comp_type { /* XXX should be a define */ + EC_COMP_UNKNOWN = 0x1, + EC_COMP_FULL = 0x2, + EC_COMP_PARTIAL = 0x4, + EC_COMP_ALL = 0x7, +}; + +struct ec_comp_item; + +TAILQ_HEAD(ec_comp_item_list, ec_comp_item); + +struct ec_comp_group { + TAILQ_ENTRY(ec_comp_group) next; + const struct ec_node *node; + struct ec_comp_item_list items; + struct ec_parse *state; + struct ec_keyval *attrs; +}; + +TAILQ_HEAD(ec_comp_group_list, ec_comp_group); + +struct ec_comp { + unsigned count; + unsigned count_full; + unsigned count_partial; + unsigned count_unknown; + struct ec_parse *cur_state; + struct ec_comp_group *cur_group; + struct ec_comp_group_list groups; + struct ec_keyval *attrs; +}; + +/* + * return a comp object filled with items + * return NULL on error (nomem?) + */ +struct ec_comp *ec_node_complete(const struct ec_node *node, + const char *str); +struct ec_comp *ec_node_complete_strvec(const struct ec_node *node, + const struct ec_strvec *strvec); + +/* internal: used by nodes */ +int ec_node_complete_child(const struct ec_node *node, + struct ec_comp *comp, + const struct ec_strvec *strvec); + +/** + * Create a completion object (list of completion items). + * + * + */ +struct ec_comp *ec_comp(struct ec_parse *state); + +/** + * Free a completion object and all its items. + * + * + */ +void ec_comp_free(struct ec_comp *comp); + +/** + * + * + * + */ +void ec_comp_dump(FILE *out, + const struct ec_comp *comp); + +/** + * Merge items contained in 'from' into 'to' + * + * The 'from' comp struct is freed. + */ +int ec_comp_merge(struct ec_comp *to, + struct ec_comp *from); + +struct ec_parse *ec_comp_get_state(struct ec_comp *comp); + +/* shortcut for ec_comp_item() + ec_comp_item_add() */ +int ec_comp_add_item(struct ec_comp *comp, + const struct ec_node *node, + struct ec_comp_item **p_item, + enum ec_comp_type type, + const char *start, const char *full); + +/** + * + */ +int ec_comp_item_set_str(struct ec_comp_item *item, + const char *str); + +/** + * Get the string value of a completion item. + * + * + */ +const char * +ec_comp_item_get_str(const struct ec_comp_item *item); + +/** + * Get the display string value of a completion item. + * + * + */ +const char * +ec_comp_item_get_display(const struct ec_comp_item *item); + +/** + * Get the completion string value of a completion item. + * + * + */ +const char * +ec_comp_item_get_completion(const struct ec_comp_item *item); + +/** + * Get the group of a completion item. + * + * + */ +const struct ec_comp_group * +ec_comp_item_get_grp(const struct ec_comp_item *item); + +/** + * Get the type of a completion item. + * + * + */ +enum ec_comp_type +ec_comp_item_get_type(const struct ec_comp_item *item); + +/** + * Get the node associated to a completion item. + * + * + */ +const struct ec_node * +ec_comp_item_get_node(const struct ec_comp_item *item); + +/** + * Set the display value of an item. + * + * + */ +int ec_comp_item_set_display(struct ec_comp_item *item, + const char *display); + +/** + * Set the completion value of an item. + * + * + */ +int ec_comp_item_set_completion(struct ec_comp_item *item, + const char *completion); + +/** + * + * + * + */ +int +ec_node_complete_unknown(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec); + +/** + * + * + * + */ +unsigned int ec_comp_count( + const struct ec_comp *comp, + enum ec_comp_type flags); + +/** + * + * + * + */ +struct ec_comp_iter { + enum ec_comp_type type; + struct ec_comp *comp; + struct ec_comp_group *cur_node; + struct ec_comp_item *cur_match; +}; + +/** + * + * + * + */ +struct ec_comp_iter * +ec_comp_iter(struct ec_comp *comp, + enum ec_comp_type type); + +/** + * + * + * + */ +struct ec_comp_item *ec_comp_iter_next( + struct ec_comp_iter *iter); + +/** + * + * + * + */ +void ec_comp_iter_free(struct ec_comp_iter *iter); + + +#endif diff --git a/libecoli/ecoli_config.c b/libecoli/ecoli_config.c new file mode 100644 index 0000000..0698f07 --- /dev/null +++ b/libecoli/ecoli_config.c @@ -0,0 +1,1153 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(config); + +const char *ec_config_reserved_keys[] = { + "id", + "attrs", + "help", + "type", +}; + +static int +__ec_config_dump(FILE *out, const char *key, const struct ec_config *config, + size_t indent); +static int +ec_config_dict_validate(const struct ec_keyval *dict, + const struct ec_config_schema *schema); + +bool +ec_config_key_is_reserved(const char *name) +{ + size_t i; + + for (i = 0; i < EC_COUNT_OF(ec_config_reserved_keys); i++) { + if (!strcmp(name, ec_config_reserved_keys[i])) + return true; + } + return false; +} + +/* return ec_value type as a string */ +static const char * +ec_config_type_str(enum ec_config_type type) +{ + switch (type) { + case EC_CONFIG_TYPE_BOOL: return "bool"; + case EC_CONFIG_TYPE_INT64: return "int64"; + case EC_CONFIG_TYPE_UINT64: return "uint64"; + case EC_CONFIG_TYPE_STRING: return "string"; + case EC_CONFIG_TYPE_NODE: return "node"; + case EC_CONFIG_TYPE_LIST: return "list"; + case EC_CONFIG_TYPE_DICT: return "dict"; + default: return "unknown"; + } +} + +static size_t +ec_config_schema_len(const struct ec_config_schema *schema) +{ + size_t i; + + if (schema == NULL) + return 0; + for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) + ; + return i; +} + +static int +__ec_config_schema_validate(const struct ec_config_schema *schema, + enum ec_config_type type) +{ + size_t i, j; + int ret; + + if (type == EC_CONFIG_TYPE_LIST) { + if (schema[0].key != NULL) { + errno = EINVAL; + EC_LOG(EC_LOG_ERR, "list schema key must be NULL\n"); + return -1; + } + } else if (type == EC_CONFIG_TYPE_DICT) { + for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) { + if (schema[i].key == NULL) { + errno = EINVAL; + EC_LOG(EC_LOG_ERR, + "dict schema key should not be NULL\n"); + return -1; + } + } + } else { + errno = EINVAL; + EC_LOG(EC_LOG_ERR, "invalid schema type\n"); + return -1; + } + + for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) { + if (schema[i].key != NULL && + ec_config_key_is_reserved(schema[i].key)) { + errno = EINVAL; + EC_LOG(EC_LOG_ERR, + "key name <%s> is reserved\n", schema[i].key); + return -1; + } + /* check for duplicate name if more than one element */ + for (j = i + 1; schema[j].type != EC_CONFIG_TYPE_NONE; j++) { + if (!strcmp(schema[i].key, schema[j].key)) { + errno = EEXIST; + EC_LOG(EC_LOG_ERR, + "duplicate key <%s> in schema\n", + schema[i].key); + return -1; + } + } + + switch (schema[i].type) { + case EC_CONFIG_TYPE_BOOL: + case EC_CONFIG_TYPE_INT64: + case EC_CONFIG_TYPE_UINT64: + case EC_CONFIG_TYPE_STRING: + case EC_CONFIG_TYPE_NODE: + if (schema[i].subschema != NULL || ec_config_schema_len( + schema[i].subschema) != 0) { + errno = EINVAL; + EC_LOG(EC_LOG_ERR, + "key <%s> should not have subtype/subschema\n", + schema[i].key); + return -1; + } + break; + case EC_CONFIG_TYPE_LIST: + if (schema[i].subschema == NULL || ec_config_schema_len( + schema[i].subschema) != 1) { + errno = EINVAL; + EC_LOG(EC_LOG_ERR, + "key <%s> must have subschema of length 1\n", + schema[i].key); + return -1; + } + break; + case EC_CONFIG_TYPE_DICT: + if (schema[i].subschema == NULL || ec_config_schema_len( + schema[i].subschema) == 0) { + errno = EINVAL; + EC_LOG(EC_LOG_ERR, + "key <%s> must have subschema\n", + schema[i].key); + return -1; + } + break; + default: + EC_LOG(EC_LOG_ERR, "invalid type for key <%s>\n", + schema[i].key); + errno = EINVAL; + return -1; + } + + if (schema[i].subschema == NULL) + continue; + + ret = __ec_config_schema_validate(schema[i].subschema, + schema[i].type); + if (ret < 0) { + EC_LOG(EC_LOG_ERR, "cannot parse subschema %s%s\n", + schema[i].key ? "key=" : "", + schema[i].key ? : ""); + return ret; + } + } + + return 0; +} + +int +ec_config_schema_validate(const struct ec_config_schema *schema) +{ + return __ec_config_schema_validate(schema, EC_CONFIG_TYPE_DICT); +} + +static void +__ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema, + size_t indent) +{ + size_t i; + + for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) { + fprintf(out, "%*s" "%s%s%stype=%s desc='%s'\n", + (int)indent * 4, "", + schema[i].key ? "key=": "", + schema[i].key ? : "", + schema[i].key ? " ": "", + ec_config_type_str(schema[i].type), + schema[i].desc); + if (schema[i].subschema == NULL) + continue; + __ec_config_schema_dump(out, schema[i].subschema, + indent + 1); + } +} + +void +ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema) +{ + fprintf(out, "------------------- schema dump:\n"); + + if (schema == NULL) { + fprintf(out, "no schema\n"); + return; + } + + __ec_config_schema_dump(out, schema, 0); +} + +enum ec_config_type ec_config_get_type(const struct ec_config *config) +{ + return config->type; +} + +struct ec_config * +ec_config_bool(bool boolean) +{ + struct ec_config *value = NULL; + + value = ec_calloc(1, sizeof(*value)); + if (value == NULL) + return NULL; + + value->type = EC_CONFIG_TYPE_BOOL; + value->boolean = boolean; + + return value; +} + +struct ec_config * +ec_config_i64(int64_t i64) +{ + struct ec_config *value = NULL; + + value = ec_calloc(1, sizeof(*value)); + if (value == NULL) + return NULL; + + value->type = EC_CONFIG_TYPE_INT64; + value->i64 = i64; + + return value; +} + +struct ec_config * +ec_config_u64(uint64_t u64) +{ + struct ec_config *value = NULL; + + value = ec_calloc(1, sizeof(*value)); + if (value == NULL) + return NULL; + + value->type = EC_CONFIG_TYPE_UINT64; + value->u64 = u64; + + return value; +} + +/* duplicate string */ +struct ec_config * +ec_config_string(const char *string) +{ + struct ec_config *value = NULL; + char *s = NULL; + + if (string == NULL) + goto fail; + + s = ec_strdup(string); + if (s == NULL) + goto fail; + + value = ec_calloc(1, sizeof(*value)); + if (value == NULL) + goto fail; + + value->type = EC_CONFIG_TYPE_STRING; + value->string = s; + + return value; + +fail: + ec_free(value); + ec_free(s); + return NULL; +} + +/* "consume" the node */ +struct ec_config * +ec_config_node(struct ec_node *node) +{ + struct ec_config *value = NULL; + + if (node == NULL) + goto fail; + + value = ec_calloc(1, sizeof(*value)); + if (value == NULL) + goto fail; + + value->type = EC_CONFIG_TYPE_NODE; + value->node = node; + + return value; + +fail: + ec_node_free(node); + ec_free(value); + return NULL; +} + +struct ec_config * +ec_config_dict(void) +{ + struct ec_config *value = NULL; + struct ec_keyval *dict = NULL; + + dict = ec_keyval(); + if (dict == NULL) + goto fail; + + value = ec_calloc(1, sizeof(*value)); + if (value == NULL) + goto fail; + + value->type = EC_CONFIG_TYPE_DICT; + value->dict = dict; + + return value; + +fail: + ec_keyval_free(dict); + ec_free(value); + return NULL; +} + +struct ec_config * +ec_config_list(void) +{ + struct ec_config *value = NULL; + + value = ec_calloc(1, sizeof(*value)); + if (value == NULL) + return NULL; + + value->type = EC_CONFIG_TYPE_LIST; + TAILQ_INIT(&value->list); + + return value; +} + +const struct ec_config_schema * +ec_config_schema_lookup(const struct ec_config_schema *schema, + const char *key) +{ + size_t i; + + for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) { + if (!strcmp(key, schema[i].key)) + return &schema[i]; + } + + errno = ENOENT; + return NULL; +} + +enum ec_config_type +ec_config_schema_type(const struct ec_config_schema *schema_elt) +{ + return schema_elt->type; +} + +const struct ec_config_schema * +ec_config_schema_sub(const struct ec_config_schema *schema_elt) +{ + return schema_elt->subschema; +} + +void +ec_config_free(struct ec_config *value) +{ + if (value == NULL) + return; + + switch (value->type) { + case EC_CONFIG_TYPE_STRING: + ec_free(value->string); + break; + case EC_CONFIG_TYPE_NODE: + ec_node_free(value->node); + break; + case EC_CONFIG_TYPE_LIST: + while (!TAILQ_EMPTY(&value->list)) { + struct ec_config *v; + v = TAILQ_FIRST(&value->list); + TAILQ_REMOVE(&value->list, v, next); + ec_config_free(v); + } + break; + case EC_CONFIG_TYPE_DICT: + ec_keyval_free(value->dict); + break; + default: + break; + } + + ec_free(value); +} + +static int +ec_config_list_cmp(const struct ec_config_list *list1, + const struct ec_config_list *list2) +{ + const struct ec_config *v1, *v2; + + for (v1 = TAILQ_FIRST(list1), v2 = TAILQ_FIRST(list2); + v1 != NULL && v2 != NULL; + v1 = TAILQ_NEXT(v1, next), v2 = TAILQ_NEXT(v2, next)) { + if (ec_config_cmp(v1, v2)) + return -1; + } + if (v1 != NULL || v2 != NULL) + return -1; + + return 0; +} + +/* XXX -> ec_keyval_cmp() */ +static int +ec_config_dict_cmp(const struct ec_keyval *d1, + const struct ec_keyval *d2) +{ + const struct ec_config *v1, *v2; + struct ec_keyval_iter *iter = NULL; + const char *key; + + if (ec_keyval_len(d1) != ec_keyval_len(d2)) + return -1; + + for (iter = ec_keyval_iter(d1); + ec_keyval_iter_valid(iter); + ec_keyval_iter_next(iter)) { + key = ec_keyval_iter_get_key(iter); + v1 = ec_keyval_iter_get_val(iter); + v2 = ec_keyval_get(d2, key); + + if (ec_config_cmp(v1, v2)) + goto fail; + } + + ec_keyval_iter_free(iter); + return 0; + +fail: + ec_keyval_iter_free(iter); + return -1; +} + +int +ec_config_cmp(const struct ec_config *value1, + const struct ec_config *value2) +{ + if (value1 == NULL || value2 == NULL) { + errno = EINVAL; + return -1; + } + + if (value1->type != value2->type) + return -1; + + switch (value1->type) { + case EC_CONFIG_TYPE_BOOL: + if (value1->boolean == value2->boolean) + return 0; + case EC_CONFIG_TYPE_INT64: + if (value1->i64 == value2->i64) + return 0; + case EC_CONFIG_TYPE_UINT64: + if (value1->u64 == value2->u64) + return 0; + case EC_CONFIG_TYPE_STRING: + if (!strcmp(value1->string, value2->string)) + return 0; + case EC_CONFIG_TYPE_NODE: + if (value1->node == value2->node) + return 0; + case EC_CONFIG_TYPE_LIST: + return ec_config_list_cmp(&value1->list, &value2->list); + case EC_CONFIG_TYPE_DICT: + return ec_config_dict_cmp(value1->dict, value2->dict); + default: + break; + } + + return -1; +} + +static int +ec_config_list_validate(const struct ec_config_list *list, + const struct ec_config_schema *sch) +{ + const struct ec_config *value; + + TAILQ_FOREACH(value, list, next) { + if (value->type != sch->type) { + errno = EBADMSG; + return -1; + } + + if (value->type == EC_CONFIG_TYPE_LIST) { + if (ec_config_list_validate(&value->list, + sch->subschema) < 0) + return -1; + } else if (value->type == EC_CONFIG_TYPE_DICT) { + if (ec_config_dict_validate(value->dict, + sch->subschema) < 0) + return -1; + } + } + + return 0; +} + +static int +ec_config_dict_validate(const struct ec_keyval *dict, + const struct ec_config_schema *schema) +{ + const struct ec_config *value; + struct ec_keyval_iter *iter = NULL; + const struct ec_config_schema *sch; + const char *key; + + for (iter = ec_keyval_iter(dict); + ec_keyval_iter_valid(iter); + ec_keyval_iter_next(iter)) { + + key = ec_keyval_iter_get_key(iter); + value = ec_keyval_iter_get_val(iter); + sch = ec_config_schema_lookup(schema, key); + if (sch == NULL || sch->type != value->type) { + errno = EBADMSG; + goto fail; + } + if (value->type == EC_CONFIG_TYPE_LIST) { + if (ec_config_list_validate(&value->list, + sch->subschema) < 0) + goto fail; + } else if (value->type == EC_CONFIG_TYPE_DICT) { + if (ec_config_dict_validate(value->dict, + sch->subschema) < 0) + goto fail; + } + } + + ec_keyval_iter_free(iter); + return 0; + +fail: + ec_keyval_iter_free(iter); + return -1; +} + +int +ec_config_validate(const struct ec_config *dict, + const struct ec_config_schema *schema) +{ + if (dict->type != EC_CONFIG_TYPE_DICT || schema == NULL) { + errno = EINVAL; + goto fail; + } + + if (ec_config_dict_validate(dict->dict, schema) < 0) + goto fail; + + return 0 +; +fail: + return -1; +} + +struct ec_config * +ec_config_dict_get(const struct ec_config *config, const char *key) +{ + if (config == NULL) { + errno = EINVAL; + return NULL; + } + + if (config->type != EC_CONFIG_TYPE_DICT) { + errno = EINVAL; + return NULL; + } + + return ec_keyval_get(config->dict, key); +} + +struct ec_config * +ec_config_list_first(struct ec_config *list) +{ + if (list == NULL || list->type != EC_CONFIG_TYPE_LIST) { + errno = EINVAL; + return NULL; + } + + return TAILQ_FIRST(&list->list); +} + +struct ec_config * +ec_config_list_next(struct ec_config *list, struct ec_config *config) +{ + (void)list; + return TAILQ_NEXT(config, next); +} + +/* value is consumed */ +int ec_config_dict_set(struct ec_config *config, const char *key, + struct ec_config *value) +{ + void (*free_cb)(struct ec_config *) = ec_config_free; + + if (config == NULL || key == NULL || value == NULL) { + errno = EINVAL; + goto fail; + } + if (config->type != EC_CONFIG_TYPE_DICT) { + errno = EINVAL; + goto fail; + } + + return ec_keyval_set(config->dict, key, value, + (void (*)(void *))free_cb); + +fail: + ec_config_free(value); + return -1; +} + +int ec_config_dict_del(struct ec_config *config, const char *key) +{ + if (config == NULL || key == NULL) { + errno = EINVAL; + return -1; + } + if (config->type != EC_CONFIG_TYPE_DICT) { + errno = EINVAL; + return -1; + } + + return ec_keyval_del(config->dict, key); +} + +/* value is consumed */ +int +ec_config_list_add(struct ec_config *list, + struct ec_config *value) +{ + if (list == NULL || list->type != EC_CONFIG_TYPE_LIST || value == NULL) { + errno = EINVAL; + goto fail; + } + + TAILQ_INSERT_TAIL(&list->list, value, next); + + return 0; + +fail: + ec_config_free(value); + return -1; +} + +int ec_config_list_del(struct ec_config *list, struct ec_config *config) +{ + if (list == NULL || list->type != EC_CONFIG_TYPE_LIST) { + errno = EINVAL; + return -1; + } + + TAILQ_REMOVE(&list->list, config, next); + ec_config_free(config); + return 0; +} + +static struct ec_config * +ec_config_list_dup(const struct ec_config_list *list) +{ + struct ec_config *dup = NULL, *v, *value; + + dup = ec_config_list(); + if (dup == NULL) + goto fail; + + TAILQ_FOREACH(v, list, next) { + value = ec_config_dup(v); + if (value == NULL) + goto fail; + if (ec_config_list_add(dup, value) < 0) + goto fail; + } + + return dup; + +fail: + ec_config_free(dup); + return NULL; +} + +static struct ec_config * +ec_config_dict_dup(const struct ec_keyval *dict) +{ + struct ec_config *dup = NULL, *value; + struct ec_keyval_iter *iter = NULL; + const char *key; + + dup = ec_config_dict(); + if (dup == NULL) + goto fail; + + for (iter = ec_keyval_iter(dict); + ec_keyval_iter_valid(iter); + ec_keyval_iter_next(iter)) { + key = ec_keyval_iter_get_key(iter); + value = ec_config_dup(ec_keyval_iter_get_val(iter)); + if (value == NULL) + goto fail; + if (ec_config_dict_set(dup, key, value) < 0) + goto fail; + } + ec_keyval_iter_free(iter); + + return dup; + +fail: + ec_config_free(dup); + ec_keyval_iter_free(iter); + return NULL; +} + +struct ec_config * +ec_config_dup(const struct ec_config *config) +{ + if (config == NULL) { + errno = EINVAL; + return NULL; + } + + switch (config->type) { + case EC_CONFIG_TYPE_BOOL: + return ec_config_bool(config->boolean); + case EC_CONFIG_TYPE_INT64: + return ec_config_i64(config->i64); + case EC_CONFIG_TYPE_UINT64: + return ec_config_u64(config->u64); + case EC_CONFIG_TYPE_STRING: + return ec_config_string(config->string); + case EC_CONFIG_TYPE_NODE: + return ec_config_node(ec_node_clone(config->node)); + case EC_CONFIG_TYPE_LIST: + return ec_config_list_dup(&config->list); + case EC_CONFIG_TYPE_DICT: + return ec_config_dict_dup(config->dict); + default: + errno = EINVAL; + break; + } + + return NULL; +} + +static int +ec_config_list_dump(FILE *out, const char *key, + const struct ec_config_list *list, size_t indent) +{ + const struct ec_config *v; + + fprintf(out, "%*s" "%s%s%stype=list\n", (int)indent * 4, "", + key ? "key=": "", + key ? key: "", + key ? " ": ""); + + TAILQ_FOREACH(v, list, next) { + if (__ec_config_dump(out, NULL, v, indent + 1) < 0) + return -1; + } + + return 0; +} + +static int +ec_config_dict_dump(FILE *out, const char *key, const struct ec_keyval *dict, + size_t indent) +{ + const struct ec_config *value; + struct ec_keyval_iter *iter; + const char *k; + + fprintf(out, "%*s" "%s%s%stype=dict\n", (int)indent * 4, "", + key ? "key=": "", + key ? key: "", + key ? " ": ""); + + for (iter = ec_keyval_iter(dict); + ec_keyval_iter_valid(iter); + ec_keyval_iter_next(iter)) { + k = ec_keyval_iter_get_key(iter); + value = ec_keyval_iter_get_val(iter); + if (__ec_config_dump(out, k, value, indent + 1) < 0) + goto fail; + } + ec_keyval_iter_free(iter); + return 0; + +fail: + ec_keyval_iter_free(iter); + return -1; +} + +static int +__ec_config_dump(FILE *out, const char *key, const struct ec_config *value, + size_t indent) +{ + char *val_str = NULL; + + switch (value->type) { + case EC_CONFIG_TYPE_BOOL: + if (value->boolean) + ec_asprintf(&val_str, "true"); + else + ec_asprintf(&val_str, "false"); + break; + case EC_CONFIG_TYPE_INT64: + ec_asprintf(&val_str, "%"PRIu64, value->u64); + break; + case EC_CONFIG_TYPE_UINT64: + ec_asprintf(&val_str, "%"PRIi64, value->i64); + break; + case EC_CONFIG_TYPE_STRING: + ec_asprintf(&val_str, "%s", value->string); + break; + case EC_CONFIG_TYPE_NODE: + ec_asprintf(&val_str, "%p", value->node); + break; + case EC_CONFIG_TYPE_LIST: + return ec_config_list_dump(out, key, &value->list, indent); + case EC_CONFIG_TYPE_DICT: + return ec_config_dict_dump(out, key, value->dict, indent); + default: + errno = EINVAL; + break; + } + + /* errno is already set on error */ + if (val_str == NULL) + goto fail; + + fprintf(out, "%*s" "%s%s%stype=%s val=%s\n", (int)indent * 4, "", + key ? "key=": "", + key ? key: "", + key ? " ": "", + ec_config_type_str(value->type), val_str); + + ec_free(val_str); + return 0; + +fail: + ec_free(val_str); + return -1; +} + +void +ec_config_dump(FILE *out, const struct ec_config *config) +{ + fprintf(out, "------------------- config dump:\n"); + + if (config == NULL) { + fprintf(out, "no config\n"); + return; + } + + if (__ec_config_dump(out, NULL, config, 0) < 0) + fprintf(out, "error while dumping\n"); +} + +/* LCOV_EXCL_START */ +static const struct ec_config_schema sch_intlist_elt[] = { + { + .desc = "This is a description for int", + .type = EC_CONFIG_TYPE_INT64, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static const struct ec_config_schema sch_dict[] = { + { + .key = "my_int", + .desc = "This is a description for int", + .type = EC_CONFIG_TYPE_INT64, + }, + { + .key = "my_int2", + .desc = "This is a description for int2", + .type = EC_CONFIG_TYPE_INT64, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static const struct ec_config_schema sch_dictlist_elt[] = { + { + .desc = "This is a description for dict", + .type = EC_CONFIG_TYPE_DICT, + .subschema = sch_dict, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static const struct ec_config_schema sch_baseconfig[] = { + { + .key = "my_bool", + .desc = "This is a description for bool", + .type = EC_CONFIG_TYPE_BOOL, + }, + { + .key = "my_int", + .desc = "This is a description for int", + .type = EC_CONFIG_TYPE_INT64, + }, + { + .key = "my_string", + .desc = "This is a description for string", + .type = EC_CONFIG_TYPE_STRING, + }, + { + .key = "my_node", + .desc = "This is a description for node", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .key = "my_intlist", + .desc = "This is a description for list", + .type = EC_CONFIG_TYPE_LIST, + .subschema = sch_intlist_elt, + }, + { + .key = "my_dictlist", + .desc = "This is a description for list", + .type = EC_CONFIG_TYPE_LIST, + .subschema = sch_dictlist_elt, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_config_testcase(void) +{ + struct ec_node *node = NULL; + struct ec_keyval *dict = NULL; + const struct ec_config *value = NULL; + struct ec_config *config = NULL, *config2 = NULL; + struct ec_config *list = NULL, *subconfig = NULL; + struct ec_config *list_, *config_; + int testres = 0; + int ret; + + testres |= EC_TEST_CHECK(ec_config_key_is_reserved("id"), + "'id' should be reserved"); + testres |= EC_TEST_CHECK(!ec_config_key_is_reserved("foo"), + "'foo' should not be reserved"); + + node = ec_node("empty", EC_NO_ID); + if (node == NULL) + goto fail; + + if (ec_config_schema_validate(sch_baseconfig) < 0) { + EC_LOG(EC_LOG_ERR, "invalid config schema\n"); + goto fail; + } + + ec_config_schema_dump(stdout, sch_baseconfig); + + config = ec_config_dict(); + if (config == NULL) + goto fail; + + ret = ec_config_dict_set(config, "my_bool", ec_config_bool(true)); + testres |= EC_TEST_CHECK(ret == 0, "cannot set boolean"); + value = ec_config_dict_get(config, "my_bool"); + testres |= EC_TEST_CHECK( + value != NULL && + value->type == EC_CONFIG_TYPE_BOOL && + value->boolean == true, + "unexpected boolean value"); + + ret = ec_config_dict_set(config, "my_int", ec_config_i64(1234)); + testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); + value = ec_config_dict_get(config, "my_int"); + testres |= EC_TEST_CHECK( + value != NULL && + value->type == EC_CONFIG_TYPE_INT64 && + value->i64 == 1234, + "unexpected int value"); + + testres |= EC_TEST_CHECK( + ec_config_validate(config, sch_baseconfig) == 0, + "cannot validate config\n"); + + ret = ec_config_dict_set(config, "my_string", ec_config_string("toto")); + testres |= EC_TEST_CHECK(ret == 0, "cannot set string"); + value = ec_config_dict_get(config, "my_string"); + testres |= EC_TEST_CHECK( + value != NULL && + value->type == EC_CONFIG_TYPE_STRING && + !strcmp(value->string, "toto"), + "unexpected string value"); + + list = ec_config_list(); + if (list == NULL) + goto fail; + + subconfig = ec_config_dict(); + if (subconfig == NULL) + goto fail; + + ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(1)); + testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); + value = ec_config_dict_get(subconfig, "my_int"); + testres |= EC_TEST_CHECK( + value != NULL && + value->type == EC_CONFIG_TYPE_INT64 && + value->i64 == 1, + "unexpected int value"); + + ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(2)); + testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); + value = ec_config_dict_get(subconfig, "my_int2"); + testres |= EC_TEST_CHECK( + value != NULL && + value->type == EC_CONFIG_TYPE_INT64 && + value->i64 == 2, + "unexpected int value"); + + testres |= EC_TEST_CHECK( + ec_config_validate(subconfig, sch_dict) == 0, + "cannot validate subconfig\n"); + + ret = ec_config_list_add(list, subconfig); + subconfig = NULL; /* freed */ + testres |= EC_TEST_CHECK(ret == 0, "cannot add in list"); + + subconfig = ec_config_dict(); + if (subconfig == NULL) + goto fail; + + ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(3)); + testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); + value = ec_config_dict_get(subconfig, "my_int"); + testres |= EC_TEST_CHECK( + value != NULL && + value->type == EC_CONFIG_TYPE_INT64 && + value->i64 == 3, + "unexpected int value"); + + ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(4)); + testres |= EC_TEST_CHECK(ret == 0, "cannot set int"); + value = ec_config_dict_get(subconfig, "my_int2"); + testres |= EC_TEST_CHECK( + value != NULL && + value->type == EC_CONFIG_TYPE_INT64 && + value->i64 == 4, + "unexpected int value"); + + testres |= EC_TEST_CHECK( + ec_config_validate(subconfig, sch_dict) == 0, + "cannot validate subconfig\n"); + + ret = ec_config_list_add(list, subconfig); + subconfig = NULL; /* freed */ + testres |= EC_TEST_CHECK(ret == 0, "cannot add in list"); + + ret = ec_config_dict_set(config, "my_dictlist", list); + list = NULL; + testres |= EC_TEST_CHECK(ret == 0, "cannot set list"); + + testres |= EC_TEST_CHECK( + ec_config_validate(config, sch_baseconfig) == 0, + "cannot validate config\n"); + + list_ = ec_config_dict_get(config, "my_dictlist"); + for (config_ = ec_config_list_first(list_); config_ != NULL; + config_ = ec_config_list_next(list_, config_)) { + ec_config_dump(stdout, config_); + } + + ec_config_dump(stdout, config); + + config2 = ec_config_dup(config); + testres |= EC_TEST_CHECK(config2 != NULL, "cannot duplicate config"); + testres |= EC_TEST_CHECK( + ec_config_cmp(config, config2) == 0, + "fail to compare config"); + ec_config_free(config2); + config2 = NULL; + + /* remove the first element */ + ec_config_list_del(list_, ec_config_list_first(list_)); + testres |= EC_TEST_CHECK( + ec_config_validate(config, sch_baseconfig) == 0, + "cannot validate config\n"); + + ec_config_dump(stdout, config); + + ec_config_free(list); + ec_config_free(subconfig); + ec_config_free(config); + ec_keyval_free(dict); + ec_node_free(node); + + return testres; + +fail: + ec_config_free(list); + ec_config_free(subconfig); + ec_config_free(config); + ec_config_free(config2); + ec_keyval_free(dict); + ec_node_free(node); + + return -1; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_config_test = { + .name = "config", + .test = ec_config_testcase, +}; + +EC_TEST_REGISTER(ec_config_test); diff --git a/libecoli/ecoli_config.h b/libecoli/ecoli_config.h new file mode 100644 index 0000000..9d7c628 --- /dev/null +++ b/libecoli/ecoli_config.h @@ -0,0 +1,392 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#ifndef ECOLI_CONFIG_ +#define ECOLI_CONFIG_ + +#include +#include +#include +#include + +#ifndef EC_COUNT_OF //XXX +#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \ + ((size_t)(!(sizeof(x) % sizeof(0[x]))))) +#endif + +struct ec_config; +struct ec_keyval; + +/** + * The type identifier for a config value. + */ +enum ec_config_type { + EC_CONFIG_TYPE_NONE = 0, + EC_CONFIG_TYPE_BOOL, + EC_CONFIG_TYPE_INT64, + EC_CONFIG_TYPE_UINT64, + EC_CONFIG_TYPE_STRING, + EC_CONFIG_TYPE_NODE, + EC_CONFIG_TYPE_LIST, + EC_CONFIG_TYPE_DICT, +}; + +/** + * Structure describing the format of a configuration value. + * + * This structure is used in a const array which is referenced by a + * struct ec_config. Each entry of the array represents a key/value + * storage of the configuration dictionary. + */ +struct ec_config_schema { + const char *key; /**< The key string (NULL for list elts). */ + const char *desc; /**< A description of the value. */ + enum ec_config_type type; /**< Type of the value */ + + /** If type is dict or list, the schema of the dict or list + * elements. Else must be NULL. */ + const struct ec_config_schema *subschema; +}; + +TAILQ_HEAD(ec_config_list, ec_config); + +/** + * Structure storing the configuration data. + */ +struct ec_config { + /** type of value stored in the union */ + enum ec_config_type type; + + union { + bool boolean; /** Boolean value */ + int64_t i64; /** Signed integer value */ + uint64_t u64; /** Unsigned integer value */ + char *string; /** String value */ + struct ec_node *node; /** Node value */ + struct ec_keyval *dict; /** Hash table value */ + struct ec_config_list list; /** List value */ + }; + + /** + * Next in list, only valid if type is list. + */ + TAILQ_ENTRY(ec_config) next; +}; + +/* schema */ + +/** + * Validate a configuration schema array. + * + * @param schema + * Pointer to the first element of the schema array. The array + * must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE). + * @return + * 0 if the schema is valid, or -1 on error (errno is set). + */ +int ec_config_schema_validate(const struct ec_config_schema *schema); + +/** + * Dump a configuration schema array. + * + * @param out + * Output stream on which the dump will be sent. + * @param schema + * Pointer to the first element of the schema array. The array + * must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE). + */ +void ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema); + +/** + * Find a schema entry matching the key. + * + * Browse the schema array and lookup for the given key. + * + * @param schema + * Pointer to the first element of the schema array. The array + * must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE). + * @return + * The schema entry if it matches a key, or NULL if not found. + */ +const struct ec_config_schema * +ec_config_schema_lookup(const struct ec_config_schema *schema, + const char *key); + +/** + * Get the type of a schema entry. + * + * @param schema_elt + * Pointer to an element of the schema array. + * @return + * The type of the schema entry. + */ +enum ec_config_type +ec_config_schema_type(const struct ec_config_schema *schema_elt); + +/** + * Get the subschema of a schema entry. + * + * @param schema_elt + * Pointer to an element of the schema array. + * @return + * The subschema if any, or NULL. + */ +const struct ec_config_schema * +ec_config_schema_sub(const struct ec_config_schema *schema_elt); + +/** + * Check if a key name is reserved in a config dict. + * + * Some key names are reserved and should not be used in configs. + * + * @param name + * The name of the key to test. + * @return + * True if the key name is reserved and must not be used, else false. + */ +bool ec_config_key_is_reserved(const char *name); + +/** + * Array of reserved key names. + */ +extern const char *ec_config_reserved_keys[]; + + +/* config */ + +/** + * Get the type of the configuration. + * + * @param config + * The configuration. + * @return + * The configuration type. + */ +enum ec_config_type ec_config_get_type(const struct ec_config *config); + +/** + * Create a boolean configuration value. + * + * @param boolean + * The boolean value to be set. + * @return + * The configuration object, or NULL on error (errno is set). + */ +struct ec_config *ec_config_bool(bool boolean); + +/** + * Create a signed integer configuration value. + * + * @param i64 + * The signed integer value to be set. + * @return + * The configuration object, or NULL on error (errno is set). + */ +struct ec_config *ec_config_i64(int64_t i64); + +/** + * Create an unsigned configuration value. + * + * @param u64 + * The unsigned integer value to be set. + * @return + * The configuration object, or NULL on error (errno is set). + */ +struct ec_config *ec_config_u64(uint64_t u64); + +/** + * Create a string configuration value. + * + * @param string + * The string value to be set. The string is copied into the + * configuration object. + * @return + * The configuration object, or NULL on error (errno is set). + */ +struct ec_config *ec_config_string(const char *string); + +/** + * Create a node configuration value. + * + * @param node + * The node pointer to be set. The node is "consumed" by + * the function and should not be used by the caller, even + * on error. The caller can use ec_node_clone() to keep a + * reference on the node. + * @return + * The configuration object, or NULL on error (errno is set). + */ +struct ec_config *ec_config_node(struct ec_node *node); + +/** + * Create a hash table configuration value. + * + * @return + * A configuration object containing an empty hash table, or NULL on + * error (errno is set). + */ +struct ec_config *ec_config_dict(void); + +/** + * Create a list configuration value. + * + * @return + * The configuration object containing an empty list, or NULL on + * error (errno is set). + */ +struct ec_config *ec_config_list(void); + +/** + * Add a config object into a list. + * + * @param list + * The list configuration in which the value will be added. + * @param value + * The value configuration to add in the list. The value object + * will be freed when freeing the list object. On error, the + * value object is also freed. + * @return + * 0 on success, else -1 (errno is set). + */ +int ec_config_list_add(struct ec_config *list, struct ec_config *value); + +/** + * Remove an element from a list. + * + * The element is freed and should not be accessed. + * + * @param list + * The list configuration. + * @param config + * The element to remove from the list. + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_config_list_del(struct ec_config *list, struct ec_config *config); + +/** + * Validate a configuration. + * + * @param dict + * A hash table configuration to validate. + * @param schema + * Pointer to the first element of the schema array. The array + * must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE). + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_config_validate(const struct ec_config *dict, + const struct ec_config_schema *schema); + +/** + * Set a value in a hash table configuration + * + * @param dict + * A hash table configuration to validate. + * @param key + * The key to update. + * @param value + * The value to set. The value object will be freed when freeing the + * dict object. On error, the value object is also freed. + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_config_dict_set(struct ec_config *dict, const char *key, + struct ec_config *value); + +/** + * Remove an element from a hash table configuration. + * + * The element is freed and should not be accessed. + * + * @param dict + * A hash table configuration to validate. + * @param key + * The key of the configuration to delete. + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_config_dict_del(struct ec_config *config, const char *key); + +/** + * Compare two configurations. + */ +int ec_config_cmp(const struct ec_config *config1, + const struct ec_config *config2); + +/** + * Get configuration value. + */ +struct ec_config *ec_config_dict_get(const struct ec_config *config, + const char *key); + +/** + * Get the first element of a list. + * + * Example of use: + * for (config = ec_config_list_iter(list); + * config != NULL; + * config = ec_config_list_next(list, config)) { + * ... + * } + * + * @param list + * The list configuration to iterate. + * @return + * The first configuration element, or NULL on error (errno is set). + */ +struct ec_config *ec_config_list_first(struct ec_config *list); + +/** + * Get next element in list. + * + * @param list + * The list configuration beeing iterated. + * @param config + * The current configuration element. + * @return + * The next configuration element, or NULL if there is no more element. + */ +struct ec_config * +ec_config_list_next(struct ec_config *list, struct ec_config *config); + +/** + * Free a configuration. + * + * @param config + * The element to free. + */ +void ec_config_free(struct ec_config *config); + +/** + * Compare two configurations. + * + * @return + * 0 if the configurations are equal, else -1. + */ +int ec_config_cmp(const struct ec_config *value1, + const struct ec_config *value2); + +/** + * Duplicate a configuration. + * + * @param config + * The configuration to duplicate. + * @return + * The duplicated configuration, or NULL on error (errno is set). + */ +struct ec_config * +ec_config_dup(const struct ec_config *config); + +/** + * Dump a configuration. + * + * @param out + * Output stream on which the dump will be sent. + * @param config + * The configuration to dump. + */ +void ec_config_dump(FILE *out, const struct ec_config *config); + +#endif diff --git a/libecoli/ecoli_init.c b/libecoli/ecoli_init.c new file mode 100644 index 0000000..fd5c0c3 --- /dev/null +++ b/libecoli/ecoli_init.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include + +#include + +static struct ec_init_list init_list = TAILQ_HEAD_INITIALIZER(init_list); + +/* register an init function */ +void ec_init_register(struct ec_init *init) +{ + struct ec_init *cur; + + if (TAILQ_EMPTY(&init_list)) { + TAILQ_INSERT_HEAD(&init_list, init, next); + return; + } + + + TAILQ_FOREACH(cur, &init_list, next) { + if (init->priority > cur->priority) + continue; + + TAILQ_INSERT_BEFORE(cur, init, next); + return; + } + + TAILQ_INSERT_TAIL(&init_list, init, next); +} + +int ec_init(void) +{ + struct ec_init *init; + + TAILQ_FOREACH(init, &init_list, next) { + if (init->init() < 0) + return -1; + } + + return 0; +} diff --git a/libecoli/ecoli_init.h b/libecoli/ecoli_init.h new file mode 100644 index 0000000..4e8bc1e --- /dev/null +++ b/libecoli/ecoli_init.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * Register initialization routines. + */ + +#ifndef ECOLI_INIT_ +#define ECOLI_INIT_ + +#include + +#include +#include + +#define EC_INIT_REGISTER(t) \ + static void ec_init_init_##t(void); \ + static void __attribute__((constructor, used)) \ + ec_init_init_##t(void) \ + { \ + ec_init_register(&t); \ + } + +/** + * Type of init function. Return 0 on success, -1 on error. + */ +typedef int (ec_init_t)(void); + +TAILQ_HEAD(ec_init_list, ec_init); + +/** + * A structure describing a test case. + */ +struct ec_init { + TAILQ_ENTRY(ec_init) next; /**< Next in list. */ + ec_init_t *init; /**< Init function. */ + unsigned int priority; /**< Priority (0=first, 99=last) */ +}; + +/** + * Register an initialization function. + * + * @param init + * A pointer to a ec_init structure to be registered. + */ +void ec_init_register(struct ec_init *test); + +/** + * Initialize ecoli library + * + * Must be called before any other function from libecoli, except + * ec_malloc_register(). + * + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_init(void); + +#endif diff --git a/libecoli/ecoli_keyval.c b/libecoli/ecoli_keyval.c new file mode 100644 index 0000000..12fe66b --- /dev/null +++ b/libecoli/ecoli_keyval.c @@ -0,0 +1,569 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define FACTOR 3 + +EC_LOG_TYPE_REGISTER(keyval); + +static uint32_t ec_keyval_seed; + +struct ec_keyval_elt { + char *key; + void *val; + uint32_t hash; + ec_keyval_elt_free_t free; + unsigned int refcount; +}; + +struct ec_keyval_elt_ref { + LIST_ENTRY(ec_keyval_elt_ref) next; + struct ec_keyval_elt *elt; +}; + +LIST_HEAD(ec_keyval_elt_ref_list, ec_keyval_elt_ref); + +struct ec_keyval { + size_t len; + size_t table_size; + struct ec_keyval_elt_ref_list *table; +}; + +struct ec_keyval_iter { + const struct ec_keyval *keyval; + size_t cur_idx; + const struct ec_keyval_elt_ref *cur_ref; +}; + +struct ec_keyval *ec_keyval(void) +{ + struct ec_keyval *keyval; + + keyval = ec_calloc(1, sizeof(*keyval)); + if (keyval == NULL) + return NULL; + + return keyval; +} + +static struct ec_keyval_elt_ref * +ec_keyval_lookup(const struct ec_keyval *keyval, const char *key) +{ + struct ec_keyval_elt_ref *ref; + uint32_t h, mask = keyval->table_size - 1; + + if (keyval == NULL || key == NULL) { + errno = EINVAL; + return NULL; + } + if (keyval->table_size == 0) { + errno = ENOENT; + return NULL; + } + + h = ec_murmurhash3(key, strlen(key), ec_keyval_seed); + LIST_FOREACH(ref, &keyval->table[h & mask], next) { + if (strcmp(ref->elt->key, key) == 0) + return ref; + } + + errno = ENOENT; + return NULL; +} + +static void ec_keyval_elt_ref_free(struct ec_keyval_elt_ref *ref) +{ + struct ec_keyval_elt *elt; + + if (ref == NULL) + return; + + elt = ref->elt; + if (elt != NULL && --elt->refcount == 0) { + ec_free(elt->key); + if (elt->free != NULL) + elt->free(elt->val); + ec_free(elt); + } + ec_free(ref); +} + +bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key) +{ + return !!ec_keyval_lookup(keyval, key); +} + +void *ec_keyval_get(const struct ec_keyval *keyval, const char *key) +{ + struct ec_keyval_elt_ref *ref; + + ref = ec_keyval_lookup(keyval, key); + if (ref == NULL) + return NULL; + + return ref->elt->val; +} + +int ec_keyval_del(struct ec_keyval *keyval, const char *key) +{ + struct ec_keyval_elt_ref *ref; + + ref = ec_keyval_lookup(keyval, key); + if (ref == NULL) + return -1; + + /* we could resize table here */ + + LIST_REMOVE(ref, next); + ec_keyval_elt_ref_free(ref); + keyval->len--; + + return 0; +} + +static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size) +{ + struct ec_keyval_elt_ref_list *new_table; + struct ec_keyval_elt_ref *ref; + size_t i; + + if (new_size == 0 || (new_size & (new_size - 1))) { + errno = EINVAL; + return -1; + } + + new_table = ec_calloc(new_size, sizeof(*keyval->table)); + if (new_table == NULL) + return -1; + + for (i = 0; i < keyval->table_size; i++) { + while (!LIST_EMPTY(&keyval->table[i])) { + ref = LIST_FIRST(&keyval->table[i]); + LIST_REMOVE(ref, next); + LIST_INSERT_HEAD( + &new_table[ref->elt->hash & (new_size - 1)], + ref, next); + } + } + + ec_free(keyval->table); + keyval->table = new_table; + keyval->table_size = new_size; + + return 0; +} + +static int +__ec_keyval_set(struct ec_keyval *keyval, struct ec_keyval_elt_ref *ref) +{ + size_t new_size; + uint32_t mask; + int ret; + + /* remove previous entry if any */ + ec_keyval_del(keyval, ref->elt->key); + + if (keyval->len >= keyval->table_size) { + if (keyval->table_size != 0) + new_size = keyval->table_size << FACTOR; + else + new_size = 1 << FACTOR; + ret = ec_keyval_table_resize(keyval, new_size); + if (ret < 0) + return ret; + } + + mask = keyval->table_size - 1; + LIST_INSERT_HEAD(&keyval->table[ref->elt->hash & mask], ref, next); + keyval->len++; + + return 0; +} + +int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val, + ec_keyval_elt_free_t free_cb) +{ + struct ec_keyval_elt *elt = NULL; + struct ec_keyval_elt_ref *ref = NULL; + uint32_t h; + + if (keyval == NULL || key == NULL) { + errno = EINVAL; + return -1; + } + + ref = ec_calloc(1, sizeof(*ref)); + if (ref == NULL) + goto fail; + + elt = ec_calloc(1, sizeof(*elt)); + if (elt == NULL) + goto fail; + + ref->elt = elt; + elt->refcount = 1; + elt->val = val; + val = NULL; + elt->free = free_cb; + elt->key = ec_strdup(key); + if (elt->key == NULL) + goto fail; + h = ec_murmurhash3(key, strlen(key), ec_keyval_seed); + elt->hash = h; + + if (__ec_keyval_set(keyval, ref) < 0) + goto fail; + + return 0; + +fail: + if (free_cb != NULL && val != NULL) + free_cb(val); + ec_keyval_elt_ref_free(ref); + return -1; +} + +void ec_keyval_free(struct ec_keyval *keyval) +{ + struct ec_keyval_elt_ref *ref; + size_t i; + + if (keyval == NULL) + return; + + for (i = 0; i < keyval->table_size; i++) { + while (!LIST_EMPTY(&keyval->table[i])) { + ref = LIST_FIRST(&keyval->table[i]); + LIST_REMOVE(ref, next); + ec_keyval_elt_ref_free(ref); + } + } + ec_free(keyval->table); + ec_free(keyval); +} + +size_t ec_keyval_len(const struct ec_keyval *keyval) +{ + return keyval->len; +} + +void +ec_keyval_iter_free(struct ec_keyval_iter *iter) +{ + ec_free(iter); +} + +struct ec_keyval_iter * +ec_keyval_iter(const struct ec_keyval *keyval) +{ + struct ec_keyval_iter *iter = NULL; + + iter = ec_calloc(1, sizeof(*iter)); + if (iter == NULL) + return NULL; + + iter->keyval = keyval; + iter->cur_idx = 0; + iter->cur_ref = NULL; + + ec_keyval_iter_next(iter); + + return iter; +} + +void +ec_keyval_iter_next(struct ec_keyval_iter *iter) +{ + const struct ec_keyval_elt_ref *ref; + size_t i; + + i = iter->cur_idx; + if (i == iter->keyval->table_size) + return; /* no more element */ + + if (iter->cur_ref != NULL) { + ref = LIST_NEXT(iter->cur_ref, next); + if (ref != NULL) { + iter->cur_ref = ref; + return; + } + i++; + } + + for (; i < iter->keyval->table_size; i++) { + LIST_FOREACH(ref, &iter->keyval->table[i], next) { + iter->cur_idx = i; + iter->cur_ref = LIST_FIRST(&iter->keyval->table[i]); + return; + } + } + + iter->cur_idx = iter->keyval->table_size; + iter->cur_ref = NULL; +} + +bool +ec_keyval_iter_valid(const struct ec_keyval_iter *iter) +{ + if (iter == NULL || iter->cur_ref == NULL) + return false; + + return true; +} + +const char * +ec_keyval_iter_get_key(const struct ec_keyval_iter *iter) +{ + if (iter == NULL || iter->cur_ref == NULL) + return NULL; + + return iter->cur_ref->elt->key; +} + +void * +ec_keyval_iter_get_val(const struct ec_keyval_iter *iter) +{ + if (iter == NULL || iter->cur_ref == NULL) + return NULL; + + return iter->cur_ref->elt->val; +} + +void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval) +{ + struct ec_keyval_iter *iter; + + if (keyval == NULL) { + fprintf(out, "empty keyval\n"); + return; + } + + fprintf(out, "keyval:\n"); + for (iter = ec_keyval_iter(keyval); + ec_keyval_iter_valid(iter); + ec_keyval_iter_next(iter)) { + fprintf(out, " %s: %p\n", + ec_keyval_iter_get_key(iter), + ec_keyval_iter_get_val(iter)); + } + ec_keyval_iter_free(iter); +} + +struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval) +{ + struct ec_keyval *dup = NULL; + struct ec_keyval_elt_ref *ref, *dup_ref = NULL; + size_t i; + + dup = ec_keyval(); + if (dup == NULL) + return NULL; + + for (i = 0; i < keyval->table_size; i++) { + LIST_FOREACH(ref, &keyval->table[i], next) { + dup_ref = ec_calloc(1, sizeof(*ref)); + if (dup_ref == NULL) + goto fail; + dup_ref->elt = ref->elt; + ref->elt->refcount++; + + if (__ec_keyval_set(dup, dup_ref) < 0) + goto fail; + } + } + + return dup; + +fail: + ec_keyval_elt_ref_free(dup_ref); + ec_keyval_free(dup); + return NULL; +} + +static int ec_keyval_init_func(void) +{ + int fd; + ssize_t ret; + + return 0;//XXX for test reproduceability + + fd = open("/dev/urandom", 0); + if (fd == -1) { + fprintf(stderr, "failed to open /dev/urandom\n"); + return -1; + } + ret = read(fd, &ec_keyval_seed, sizeof(ec_keyval_seed)); + if (ret != sizeof(ec_keyval_seed)) { + fprintf(stderr, "failed to read /dev/urandom\n"); + return -1; + } + close(fd); + return 0; +} + +static struct ec_init ec_keyval_init = { + .init = ec_keyval_init_func, + .priority = 50, +}; + +EC_INIT_REGISTER(ec_keyval_init); + +/* LCOV_EXCL_START */ +static int ec_keyval_testcase(void) +{ + struct ec_keyval *keyval, *dup; + struct ec_keyval_iter *iter; + char *val; + size_t i, count; + int ret, testres = 0; + FILE *f = NULL; + char *buf = NULL; + size_t buflen = 0; + + keyval = ec_keyval(); + if (keyval == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create keyval\n"); + return -1; + } + + count = 0; + for (iter = ec_keyval_iter(keyval); + ec_keyval_iter_valid(iter); + ec_keyval_iter_next(iter)) { + count++; + } + ec_keyval_iter_free(iter); + testres |= EC_TEST_CHECK(count == 0, "invalid count in iterator"); + + testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0, "bad keyval len"); + ret = ec_keyval_set(keyval, "key1", "val1", NULL); + testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); + ret = ec_keyval_set(keyval, "key2", ec_strdup("val2"), ec_free_func); + testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); + testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2, "bad keyval len"); + + val = ec_keyval_get(keyval, "key1"); + testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val1"), + "invalid keyval value"); + val = ec_keyval_get(keyval, "key2"); + testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val2"), + "invalid keyval value"); + val = ec_keyval_get(keyval, "key3"); + testres |= EC_TEST_CHECK(val == NULL, "key3 should be NULL"); + + ret = ec_keyval_set(keyval, "key1", "another_val1", NULL); + testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); + ret = ec_keyval_set(keyval, "key2", ec_strdup("another_val2"), + ec_free_func); + testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); + testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2, + "bad keyval len"); + + val = ec_keyval_get(keyval, "key1"); + testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val1"), + "invalid keyval value"); + val = ec_keyval_get(keyval, "key2"); + testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val2"), + "invalid keyval value"); + testres |= EC_TEST_CHECK(ec_keyval_has_key(keyval, "key1"), + "key1 should be in keyval"); + + f = open_memstream(&buf, &buflen); + if (f == NULL) + goto fail; + ec_keyval_dump(f, NULL); + fclose(f); + f = NULL; + free(buf); + buf = NULL; + + f = open_memstream(&buf, &buflen); + if (f == NULL) + goto fail; + ec_keyval_dump(f, keyval); + fclose(f); + f = NULL; + free(buf); + buf = NULL; + + ret = ec_keyval_del(keyval, "key1"); + testres |= EC_TEST_CHECK(ret == 0, "cannot del key1"); + testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 1, + "invalid keyval len"); + ret = ec_keyval_del(keyval, "key2"); + testres |= EC_TEST_CHECK(ret == 0, "cannot del key2"); + testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0, + "invalid keyval len"); + + for (i = 0; i < 100; i++) { + char key[8]; + snprintf(key, sizeof(key), "k%zd", i); + ret = ec_keyval_set(keyval, key, "val", NULL); + testres |= EC_TEST_CHECK(ret == 0, "cannot set key"); + } + dup = ec_keyval_dup(keyval); + testres |= EC_TEST_CHECK(dup != NULL, "cannot duplicate keyval"); + if (dup != NULL) { + for (i = 0; i < 100; i++) { + char key[8]; + snprintf(key, sizeof(key), "k%zd", i); + val = ec_keyval_get(dup, key); + testres |= EC_TEST_CHECK( + val != NULL && !strcmp(val, "val"), + "invalid keyval value"); + } + ec_keyval_free(dup); + dup = NULL; + } + + count = 0; + for (iter = ec_keyval_iter(keyval); + ec_keyval_iter_valid(iter); + ec_keyval_iter_next(iter)) { + count++; + } + ec_keyval_iter_free(iter); + testres |= EC_TEST_CHECK(count == 100, "invalid count in iterator"); + + /* einval */ + ret = ec_keyval_set(keyval, NULL, "val1", NULL); + testres |= EC_TEST_CHECK(ret == -1, "should not be able to set key"); + val = ec_keyval_get(keyval, NULL); + testres |= EC_TEST_CHECK(val == NULL, "get(NULL) should no success"); + + ec_keyval_free(keyval); + + return testres; + +fail: + ec_keyval_free(keyval); + if (f) + fclose(f); + free(buf); + return -1; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_keyval_test = { + .name = "keyval", + .test = ec_keyval_testcase, +}; + +EC_TEST_REGISTER(ec_keyval_test); diff --git a/libecoli/ecoli_keyval.h b/libecoli/ecoli_keyval.h new file mode 100644 index 0000000..a3e9400 --- /dev/null +++ b/libecoli/ecoli_keyval.h @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 +#include + +typedef void (*ec_keyval_elt_free_t)(void *); + +struct ec_keyval; +struct ec_keyval_iter; + +/** + * Create a hash table. + * + * @return + * The hash table, or NULL on error (errno is set). + */ +struct ec_keyval *ec_keyval(void); + +/** + * Get a value from the hash table. + * + * @param keyval + * The hash table. + * @param key + * The key string. + * @return + * The element if it is found, or NULL on error (errno is set). + * In case of success but the element is NULL, errno is set to 0. + */ +void *ec_keyval_get(const struct ec_keyval *keyval, const char *key); + +/** + * Check if the hash table contains this key. + * + * @param keyval + * The hash table. + * @param key + * The key string. + * @return + * true if it contains the key, else false. + */ +bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key); + +/** + * Delete an object from the hash table. + * + * @param keyval + * The hash table. + * @param key + * The key string. + * @return + * 0 on success, or -1 on error (errno is set). + */ +int ec_keyval_del(struct ec_keyval *keyval, const char *key); + +/** + * Add/replace an object in the hash table. + * + * @param keyval + * The hash table. + * @param key + * The key string. + * @param val + * The pointer to be saved in the hash table. + * @param free_cb + * An optional pointer to a destructor function called when an + * object is destroyed (ec_keyval_del() or ec_keyval_free()). + * @return + * 0 on success, or -1 on error (errno is set). + * On error, the passed value is freed (free_cb(val) is called). + */ +int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val, + ec_keyval_elt_free_t free_cb); + +/** + * Free a hash table an all its objects. + * + * @param keyval + * The hash table. + */ +void ec_keyval_free(struct ec_keyval *keyval); + +/** + * Get the length of a hash table. + * + * @param keyval + * The hash table. + * @return + * The length of the hash table. + */ +size_t ec_keyval_len(const struct ec_keyval *keyval); + +/** + * Duplicate a hash table + * + * A reference counter is shared between the clones of + * hash tables so that the objects are freed only when + * the last reference is destroyed. + * + * @param keyval + * The hash table. + * @return + * The duplicated hash table, or NULL on error (errno is set). + */ +struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval); + +/** + * Dump a hash table. + * + * @param out + * The stream where the dump is sent. + * @param keyval + * The hash table. + */ +void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval); + +/** + * Iterate the elements in the hash table. + * + * The typical usage is as below: + * + * // dump elements + * for (iter = ec_keyval_iter(keyval); + * ec_keyval_iter_valid(iter); + * ec_keyval_iter_next(iter)) { + * printf(" %s: %p\n", + * ec_keyval_iter_get_key(iter), + * ec_keyval_iter_get_val(iter)); + * } + * ec_keyval_iter_free(iter); + * + * @param keyval + * The hash table. + * @return + * An iterator, or NULL on error (errno is set). + */ +struct ec_keyval_iter * +ec_keyval_iter(const struct ec_keyval *keyval); + +/** + * Make the iterator point to the next element in the hash table. + * + * @param iter + * The hash table iterator. + */ +void ec_keyval_iter_next(struct ec_keyval_iter *iter); + +/** + * Free the iterator. + * + * @param iter + * The hash table iterator. + */ +void ec_keyval_iter_free(struct ec_keyval_iter *iter); + +/** + * Check if the iterator points to a valid element. + * + * @param iter + * The hash table iterator. + * @return + * true if the element is valid, else false. + */ +bool +ec_keyval_iter_valid(const struct ec_keyval_iter *iter); + +/** + * Get the key of the current element. + * + * @param iter + * The hash table iterator. + * @return + * The current element key, or NULL if the iterator points to an + * invalid element. + */ +const char * +ec_keyval_iter_get_key(const struct ec_keyval_iter *iter); + +/** + * Get the value of the current element. + * + * @param iter + * The hash table iterator. + * @return + * The current element value, or NULL if the iterator points to an + * invalid element. + */ +void * +ec_keyval_iter_get_val(const struct ec_keyval_iter *iter); + + +#endif diff --git a/libecoli/ecoli_log.c b/libecoli/ecoli_log.c new file mode 100644 index 0000000..e7577cd --- /dev/null +++ b/libecoli/ecoli_log.c @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#define _GNU_SOURCE /* for vasprintf */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(log); + +static ec_log_t ec_log_fct = ec_log_default_cb; +static void *ec_log_opaque; + +struct ec_log_type { + char *name; + enum ec_log_level level; +}; + +static struct ec_log_type *log_types; +static size_t log_types_len; +static enum ec_log_level global_level = EC_LOG_WARNING; + +int ec_log_level_set(enum ec_log_level level) +{ + if (level > EC_LOG_DEBUG) + return -1; + global_level = level; + + return 0; +} + +enum ec_log_level ec_log_level_get(void) +{ + return global_level; +} + +int ec_log_default_cb(int type, enum ec_log_level level, void *opaque, + const char *str) +{ + (void)opaque; + + if (level > ec_log_level_get()) + return 0; + + if (fprintf(stderr, "[%d] %-12s %s", level, ec_log_name(type), str) < 0) + return -1; + + return 0; +} + +int ec_log_fct_register(ec_log_t usr_log, void *opaque) +{ + if (usr_log == NULL) { + ec_log_fct = ec_log_default_cb; + ec_log_opaque = NULL; + } else { + ec_log_fct = usr_log; + ec_log_opaque = opaque; + } + + return 0; +} + +static int +ec_log_lookup(const char *name) +{ + size_t i; + + for (i = 0; i < log_types_len; i++) { + if (log_types[i].name == NULL) + continue; + if (strcmp(name, log_types[i].name) == 0) + return i; + } + + return -1; +} + +int +ec_log_type_register(const char *name) +{ + struct ec_log_type *new_types; + char *copy; + int id; + + id = ec_log_lookup(name); + if (id >= 0) + return id; + + new_types = ec_realloc(log_types, + sizeof(*new_types) * (log_types_len + 1)); + if (new_types == NULL) + return -1; /* errno is set */ + log_types = new_types; + + copy = ec_strdup(name); + if (copy == NULL) + return -1; /* errno is set */ + + id = log_types_len++; + log_types[id].name = copy; + log_types[id].level = EC_LOG_DEBUG; + + return id; +} + +const char * +ec_log_name(int type) +{ + if (type < 0 || (unsigned int)type >= log_types_len) + return "unknown"; + return log_types[type].name; +} + +int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap) +{ + char *s; + int ret; + + /* don't use ec_vasprintf here, because it will call + * ec_malloc(), then ec_log(), ec_vasprintf()... + * -> stack overflow */ + ret = vasprintf(&s, format, ap); + if (ret < 0) + return ret; + + ret = ec_log_fct(type, level, ec_log_opaque, s); + free(s); + + return ret; +} + +int ec_log(int type, enum ec_log_level level, const char *format, ...) +{ + va_list ap; + int ret; + + va_start(ap, format); + ret = ec_vlog(type, level, format, ap); + va_end(ap); + + return ret; +} + +/* LCOV_EXCL_START */ +static int +log_cb(int type, enum ec_log_level level, void *opaque, const char *str) +{ + (void)type; + (void)level; + (void)str; + *(int *)opaque = 1; + + return 0; +} + +static int ec_log_testcase(void) +{ + ec_log_t prev_log_cb; + void *prev_opaque; + const char *logname; + int testres = 0; + int check_cb = 0; + int logtype; + int level; + int ret; + + prev_log_cb = ec_log_fct; + prev_opaque = ec_log_opaque; + + ret = ec_log_fct_register(log_cb, &check_cb); + testres |= EC_TEST_CHECK(ret == 0, + "cannot register log function\n"); + EC_LOG(LOG_ERR, "test\n"); + testres |= EC_TEST_CHECK(check_cb == 1, + "log callback was not invoked\n"); + logtype = ec_log_lookup("dsdedesdes"); + testres |= EC_TEST_CHECK(logtype == -1, + "lookup invalid name should return -1"); + logtype = ec_log_lookup("log"); + logname = ec_log_name(logtype); + testres |= EC_TEST_CHECK(logname != NULL && + !strcmp(logname, "log"), + "cannot get log name\n"); + logname = ec_log_name(-1); + testres |= EC_TEST_CHECK(logname != NULL && + !strcmp(logname, "unknown"), + "cannot get invalid log name\n"); + logname = ec_log_name(34324); + testres |= EC_TEST_CHECK(logname != NULL && + !strcmp(logname, "unknown"), + "cannot get invalid log name\n"); + level = ec_log_level_get(); + ret = ec_log_level_set(2); + testres |= EC_TEST_CHECK(ret == 0 && ec_log_level_get() == 2, + "cannot set log level\n"); + ret = ec_log_level_set(10); + testres |= EC_TEST_CHECK(ret != 0, + "should not be able to set log level\n"); + + ret = ec_log_fct_register(NULL, NULL); + ec_log_level_set(LOG_DEBUG); + EC_LOG(LOG_DEBUG, "test log\n"); + ec_log_level_set(LOG_INFO); + EC_LOG(LOG_DEBUG, "test log (not displayed)\n"); + ec_log_level_set(level); + + ec_log_fct = prev_log_cb; + ec_log_opaque = prev_opaque; + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_log_test = { + .name = "log", + .test = ec_log_testcase, +}; + +EC_TEST_REGISTER(ec_log_test); diff --git a/libecoli/ecoli_log.h b/libecoli/ecoli_log.h new file mode 100644 index 0000000..be7e380 --- /dev/null +++ b/libecoli/ecoli_log.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 + +#include + +enum ec_log_level { + EC_LOG_EMERG = 0, /* system is unusable */ + EC_LOG_ALERT = 1, /* action must be taken immediately */ + EC_LOG_CRIT = 2, /* critical conditions */ + EC_LOG_ERR = 3, /* error conditions */ + EC_LOG_WARNING = 4, /* warning conditions */ + EC_LOG_NOTICE = 5, /* normal but significant condition */ + EC_LOG_INFO = 6, /* informational */ + EC_LOG_DEBUG = 7, /* debug-level messages */ +}; + +/** + * Register a log type. + * + * This macro defines a function that will be called at startup (using + * the "constructor" attribute). This function register the named type + * passed as argument, and sets a static global variable + * "ec_log_local_type". This variable is used as the default log type + * for this file when using EC_LOG() or EC_VLOG(). + * + * This macro can be present several times in a file. In this case, the + * local log type is set to the last registered type. + * + * On error, the function aborts. + * + * @param name + * The name of the log to be registered. + */ +#define EC_LOG_TYPE_REGISTER(name) \ + static int name##_log_type; \ + static int ec_log_local_type; \ + __attribute__((constructor, used)) \ + static void ec_log_register_##name(void) \ + { \ + ec_log_local_type = ec_log_type_register(#name); \ + ec_assert_print(ec_log_local_type >= 0, \ + "cannot register log type.\n"); \ + name##_log_type = ec_log_local_type; \ + } + +/** + * User log function type. + * + * It is advised that a user-defined log function drops all messages + * that are at least as critical as ec_log_level_get(), as done by the + * default handler. + * + * @param type + * The log type identifier. + * @param level + * The log level. + * @param opaque + * The opaque pointer that was passed to ec_log_fct_register(). + * @param str + * The string to log. + * @return + * 0 on success, -1 on error (errno is set). + */ +typedef int (*ec_log_t)(int type, enum ec_log_level level, void *opaque, + const char *str); + +/** + * Register a user log function. + * + * @param usr_log + * Function pointer that will be invoked for each log call. + * If the parameter is NULL, ec_log_default_cb() is used. + * @param opaque + * Opaque pointer passed to the log function. + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_log_fct_register(ec_log_t usr_log, void *opaque); + +/** + * Register a named log type. + * + * Register a new log type, which is identified by its name. The + * function returns a log identifier associated to the log name. If the + * name is already registered, the function just returns its identifier. + * + * @param name + * The name of the log type. + * @return + * The log type identifier on success (positive or zero), -1 on + * error (errno is set). + */ +int ec_log_type_register(const char *name); + +/** + * Return the log name associated to the log type identifier. + * + * @param type + * The log type identifier. + * @return + * The name associated to the log type, or "unknown". It always return + * a valid string (never NULL). + */ +const char *ec_log_name(int type); + +/** + * Log a formatted string. + * + * @param type + * The log type identifier. + * @param level + * The log level. + * @param format + * The format string, followed by optional arguments. + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_log(int type, enum ec_log_level level, const char *format, ...) + __attribute__((format(__printf__, 3, 4))); + +/** + * Log a formatted string. + * + * @param type + * The log type identifier. + * @param level + * The log level. + * @param format + * The format string. + * @param ap + * The list of arguments. + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap); + +/** + * Log a formatted string using the local log type. + * + * This macro requires that a log type is previously register with + * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type" + * variable. + * + * @param level + * The log level. + * @param format + * The format string, followed by optional arguments. + * @return + * 0 on success, -1 on error (errno is set). + */ +#define EC_LOG(level, args...) ec_log(ec_log_local_type, level, args) + +/** + * Log a formatted string using the local log type. + * + * This macro requires that a log type is previously register with + * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type" + * variable. + * + * @param level + * The log level. + * @param format + * The format string. + * @param ap + * The list of arguments. + * @return + * 0 on success, -1 on error (errno is set). + */ +#define EC_VLOG(level, fmt, ap) ec_vlog(ec_log_local_type, level, fmt, ap) + +/** + * Default log handler. + * + * This is the default log function that is used by the library. By + * default, it prints all logs whose level is WARNING or more critical. + * This level can be changed with ec_log_level_set(). + * + * @param type + * The log type identifier. + * @param level + * The log level. + * @param opaque + * Unused. + * @param str + * The string to be logged. + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_log_default_cb(int type, enum ec_log_level level, void *opaque, + const char *str); + +/** + * Set the global log level. + * + * This level is used by the default log handler, ec_log_default_cb(). + * All messages that are at least as critical as the default level are + * displayed. + * + * It is advised + * + * @param level + * The log level to be set. + * @return + * 0 on success, -1 on error. + */ +int ec_log_level_set(enum ec_log_level level); + +/** + * Get the global log level. + * + * This level is used by the default log handler, ec_log_default_cb(). + * All messages that are at least as critical as the default level are + * displayed. + * + * @param level + * The log level to be set. + * @return + * 0 on success, -1 on error. + */ +enum ec_log_level ec_log_level_get(void); + +#endif diff --git a/libecoli/ecoli_malloc.c b/libecoli/ecoli_malloc.c new file mode 100644 index 0000000..505f49f --- /dev/null +++ b/libecoli/ecoli_malloc.c @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include + +#include +#include +#include + +EC_LOG_TYPE_REGISTER(malloc); + +static int init_done = 0; + +struct ec_malloc_handler ec_malloc_handler; + +int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free, + ec_realloc_t usr_realloc) +{ + if (usr_malloc == NULL || usr_free == NULL || usr_realloc == NULL) { + errno = EINVAL; + return -1; + } + + if (init_done) { + errno = EBUSY; + return -1; + } + + ec_malloc_handler.malloc = usr_malloc; + ec_malloc_handler.free = usr_free; + ec_malloc_handler.realloc = usr_realloc; + + return 0; +} + +void *__ec_malloc(size_t size, const char *file, unsigned int line) +{ + return ec_malloc_handler.malloc(size, file, line); +} + +void *ec_malloc_func(size_t size) +{ + return __ec_malloc(size, __FILE__, __LINE__); +} + +void __ec_free(void *ptr, const char *file, unsigned int line) +{ + ec_malloc_handler.free(ptr, file, line); +} + +void ec_free_func(void *ptr) +{ + __ec_free(ptr, __FILE__, __LINE__); +} + +void *__ec_calloc(size_t nmemb, size_t size, const char *file, + unsigned int line) +{ + void *ptr; + size_t total; + + /* check overflow */ + total = size * nmemb; + if (nmemb != 0 && size != (total / nmemb)) { + errno = ENOMEM; + return NULL; + } + + ptr = __ec_malloc(total, file, line); + if (ptr == NULL) + return NULL; + + memset(ptr, 0, total); + return ptr; +} + +void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line) +{ + return ec_malloc_handler.realloc(ptr, size, file, line); +} + +char *__ec_strdup(const char *s, const char *file, unsigned int line) +{ + size_t sz = strlen(s) + 1; + char *s2; + + s2 = __ec_malloc(sz, file, line); + if (s2 == NULL) + return NULL; + + memcpy(s2, s, sz); + + return s2; +} + +char *__ec_strndup(const char *s, size_t n, const char *file, unsigned int line) +{ + size_t sz = strnlen(s, n); + char *s2; + + s2 = __ec_malloc(sz + 1, file, line); + if (s2 == NULL) + return NULL; + + memcpy(s2, s, sz); + s2[sz] = '\0'; + + return s2; +} + +static int ec_malloc_init_func(void) +{ + init_done = 1; + return 0; +} + +static struct ec_init ec_malloc_init = { + .init = ec_malloc_init_func, + .priority = 40, +}; + +EC_INIT_REGISTER(ec_malloc_init); + +/* LCOV_EXCL_START */ +static int ec_malloc_testcase(void) +{ + int ret, testres = 0; + char *ptr, *ptr2; + + ret = ec_malloc_register(NULL, NULL, NULL); + testres |= EC_TEST_CHECK(ret == -1, + "should not be able to register NULL malloc handlers"); + ret = ec_malloc_register(__ec_malloc, __ec_free, __ec_realloc); + testres |= EC_TEST_CHECK(ret == -1, + "should not be able to register after init"); + + /* registration is tested in the test main.c */ + + ptr = ec_malloc(10); + if (ptr == NULL) + return -1; + memset(ptr, 0, 10); + ptr2 = ec_realloc(ptr, 20); + EC_TEST_CHECK(ptr2 != NULL, "cannot realloc ptr\n"); + if (ptr2 == NULL) + ec_free(ptr); + else + ec_free(ptr2); + ptr = NULL; + ptr2 = NULL; + + ptr = ec_malloc_func(10); + if (ptr == NULL) + return -1; + memset(ptr, 0, 10); + ec_free_func(ptr); + ptr = NULL; + + ptr = ec_calloc(2, (size_t)-1); + EC_TEST_CHECK(ptr == NULL, "bad overflow check in ec_calloc\n"); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_malloc_test = { + .name = "malloc", + .test = ec_malloc_testcase, +}; + +EC_TEST_REGISTER(ec_malloc_test); diff --git a/libecoli/ecoli_malloc.h b/libecoli/ecoli_malloc.h new file mode 100644 index 0000000..e80c3d9 --- /dev/null +++ b/libecoli/ecoli_malloc.h @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 +#include +#include + +/** + * Function type of malloc, passed to ec_malloc_register(). + * + * The API is the same than malloc(), excepted the file and line + * arguments. + * + * @param size + * The size of the memory area to allocate. + * @param file + * The path to the file that invoked the malloc. + * @param line + * The line in the file that invoked the malloc. + * @return + * A pointer to the allocated memory area, or NULL on error (errno + * is set). + */ +typedef void *(*ec_malloc_t)(size_t size, const char *file, unsigned int line); + +/** + * Function type of free, passed to ec_malloc_register(). + * + * The API is the same than free(), excepted the file and line + * arguments. + * + * @param ptr + * The pointer to the memory area to be freed. + * @param file + * The path to the file that invoked the malloc. + * @param line + * The line in the file that invoked the malloc. + */ +typedef void (*ec_free_t)(void *ptr, const char *file, unsigned int line); + +/** + * Function type of realloc, passed to ec_malloc_register(). + * + * The API is the same than realloc(), excepted the file and line + * arguments. + * + * @param ptr + * The pointer to the memory area to be reallocated. + * @param file + * The path to the file that invoked the malloc. + * @param line + * The line in the file that invoked the malloc. + * @return + * A pointer to the allocated memory area, or NULL on error (errno + * is set). + */ +typedef void *(*ec_realloc_t)(void *ptr, size_t size, const char *file, + unsigned int line); + +/** + * Register allocation functions. + * + * This function can be use to register another allocator + * to be used by libecoli. By default, ec_malloc(), ec_free() and + * ec_realloc() use the standard libc allocator. Another handler + * can be used for debug purposes or when running in a specific + * environment. + * + * This function must be called before ec_init(). + * + * @param usr_malloc + * A user-defined malloc function. + * @param usr_free + * A user-defined free function. + * @param usr_realloc + * A user-defined realloc function. + * @return + * 0 on success, or -1 on error (errno is set). + */ +int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free, + ec_realloc_t usr_realloc); + +struct ec_malloc_handler { + ec_malloc_t malloc; + ec_free_t free; + ec_realloc_t realloc; +}; + +extern struct ec_malloc_handler ec_malloc_handler; + +/** + * Allocate a memory area. + * + * Like malloc(), ec_malloc() allocates size bytes and returns a pointer + * to the allocated memory. The memory is not initialized. The memory is + * freed with ec_free(). + * + * @param size + * The size of the area to allocate in bytes. + * @return + * The pointer to the allocated memory, or NULL on error (errno is set). + */ +#define ec_malloc(size) ({ \ + void *ret_; \ + if (ec_malloc_handler.malloc == NULL) \ + ret_ = malloc(size); \ + else \ + ret_ = __ec_malloc(size, __FILE__, __LINE__); \ + ret_; \ + }) + +/** + * Ecoli malloc function. + * + * Use this function when the macro ec_malloc() cannot be used, + * for instance when it is passed as a callback pointer. + */ +void *ec_malloc_func(size_t size); + +/** + * Free a memory area. + * + * Like free(), ec_free() frees the area pointed by ptr, which must have + * been returned by a previous call to ec_malloc() or any other + * allocation function of this file. + * + * @param ptr + * The pointer to the memory area. + */ +#define ec_free(ptr) ({ \ + if (ec_malloc_handler.free == NULL) \ + free(ptr); \ + else \ + __ec_free(ptr, __FILE__, __LINE__); \ + }) + +/** + * Ecoli free function. + * + * Use this function when the macro ec_free() cannot be used, + * for instance when it is passed as a callback pointer. + */ +void ec_free_func(void *ptr); + +/** + * Resize an allocated memory area. + * + * @param ptr + * The pointer to the previously allocated memory area, or NULL. + * @param size + * The new size of the memory area. + * @return + * A pointer to the newly allocated memory, or NULL if the request + * fails. In that case, the original area is left untouched. + */ +#define ec_realloc(ptr, size) ({ \ + void *ret_; \ + if (ec_malloc_handler.realloc == NULL) \ + ret_ = realloc(ptr, size); \ + else \ + ret_ = __ec_realloc(ptr, size, __FILE__, __LINE__); \ + ret_; \ + }) + +/** + * Allocate and initialize an array of elements. + * + * @param n + * The number of elements. + * @param size + * The size of each element. + * @return + * The pointer to the allocated memory, or NULL on error (errno is set). + */ +#define ec_calloc(n, size) ({ \ + void *ret_; \ + if (ec_malloc_handler.malloc == NULL) \ + ret_ = calloc(n, size); \ + else \ + ret_ = __ec_calloc(n, size, __FILE__, __LINE__); \ + ret_; \ + }) + +/** + * Duplicate a string. + * + * Memory for the new string is obtained with ec_malloc(), and can be + * freed with ec_free(). + * + * @param s + * The string to be duplicated. + * @return + * The pointer to the duplicated string, or NULL on error (errno is set). + */ +#define ec_strdup(s) ({ \ + void *ret_; \ + if (ec_malloc_handler.malloc == NULL) \ + ret_ = strdup(s); \ + else \ + ret_ = __ec_strdup(s, __FILE__, __LINE__); \ + ret_; \ + }) + +/** + * Duplicate at most n bytes of a string. + * + * This function is similar to ec_strdup(), except that it copies at + * most n bytes. If s is longer than n, only n bytes are copied, and a + * terminating null byte ('\0') is added. + * + * @param s + * The string to be duplicated. + * @param n + * The maximum length of the new string. + * @return + * The pointer to the duplicated string, or NULL on error (errno is set). + */ +#define ec_strndup(s, n) ({ \ + void *ret_; \ + if (ec_malloc_handler.malloc == NULL) \ + ret_ = strndup(s, n); \ + else \ + ret_ = __ec_strndup(s, n, __FILE__, __LINE__); \ + ret_; \ + }) + +/* internal */ +void *__ec_malloc(size_t size, const char *file, unsigned int line); +void __ec_free(void *ptr, const char *file, unsigned int line); +void *__ec_calloc(size_t nmemb, size_t size, const char *file, + unsigned int line); +void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line); +char *__ec_strdup(const char *s, const char *file, unsigned int line); +char *__ec_strndup(const char *s, size_t n, const char *file, + unsigned int line); + + +#endif diff --git a/libecoli/ecoli_murmurhash.c b/libecoli/ecoli_murmurhash.c new file mode 100644 index 0000000..7aafece --- /dev/null +++ b/libecoli/ecoli_murmurhash.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include + +#include + +uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed) +{ + const uint8_t *data = (const uint8_t *)key; + const uint8_t *tail; + const int nblocks = len / 4; + uint32_t h1 = seed; + uint32_t k1; + const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); + int i; + + for (i = -nblocks; i; i++) { + k1 = blocks[i]; + + h1 = ec_murmurhash3_add32(h1, k1); + h1 = ec_murmurhash3_mix32(h1); + } + + tail = (const uint8_t *)(data + nblocks * 4); + k1 = 0; + + switch(len & 3) { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + h1 = ec_murmurhash3_add32(h1, k1); + }; + + /* finalization */ + h1 ^= len; + h1 = ec_murmurhash3_fmix32(h1); + return h1; +} diff --git a/libecoli/ecoli_murmurhash.h b/libecoli/ecoli_murmurhash.h new file mode 100644 index 0000000..6b76d34 --- /dev/null +++ b/libecoli/ecoli_murmurhash.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 + +/** Hash rotation */ +static inline uint32_t ec_murmurhash_rotl32(uint32_t x, int8_t r) +{ + return (x << r) | (x >> (32 - r)); +} + +/** Add 32-bit to the hash */ +static inline uint32_t ec_murmurhash3_add32(uint32_t h, uint32_t data) +{ + data *= 0xcc9e2d51; + data = ec_murmurhash_rotl32(data, 15); + data *= 0x1b873593; + h ^= data; + return h; +} + +/** Intermediate mix */ +static inline uint32_t ec_murmurhash3_mix32(uint32_t h) +{ + h = ec_murmurhash_rotl32(h,13); + h = h * 5 +0xe6546b64; + return h; +} + +/** Final mix: force all bits of a hash block to avalanche */ +static inline uint32_t ec_murmurhash3_fmix32(uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +/** + * Calculate a 32-bit murmurhash3 + * + * @param key + * The key (the unaligned variable-length array of bytes). + * @param len + * The length of the key, counting by bytes. + * @param seed + * Can be any 4-byte value initialization value. + * @return + * A 32-bit hash. + */ +uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed); + +#endif /* ECOLI_MURMURHASH_H_ */ diff --git a/libecoli/ecoli_node.c b/libecoli/ecoli_node.c new file mode 100644 index 0000000..c02a8de --- /dev/null +++ b/libecoli/ecoli_node.c @@ -0,0 +1,607 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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), ""), + "bad child 0"); + + testres |= EC_TEST_CHECK( + ec_node_get_children_count(node) == 2, + "bad children count\n"); + ret = ec_node_get_child(node, 0, &child, &refs); + testres |= EC_TEST_CHECK(ret == 0 && + child != NULL && + !strcmp(ec_node_type(child)->name, "str") && + !strcmp(ec_node_id(child), "id_x"), + "bad child 0"); + ret = ec_node_get_child(node, 1, &child, &refs); + testres |= EC_TEST_CHECK(ret == 0 && + child != NULL && + !strcmp(ec_node_type(child)->name, "str") && + !strcmp(ec_node_id(child), "id_y"), + "bad child 1"); + ret = ec_node_get_child(node, 2, &child, &refs); + testres |= EC_TEST_CHECK(ret != 0, + "ret should be != 0"); + testres |= EC_TEST_CHECK(child == NULL, + "child 2 should be NULL"); + + child = ec_node_find(node, "id_x"); + testres |= EC_TEST_CHECK(child != NULL && + !strcmp(ec_node_type(child)->name, "str") && + !strcmp(ec_node_id(child), "id_x") && + !strcmp(ec_node_desc(child), "x"), + "bad child id_x"); + child = ec_node_find(node, "id_dezdex"); + testres |= EC_TEST_CHECK(child == NULL, + "child with wrong id should be NULL"); + + ret = ec_keyval_set(ec_node_attrs(node), "key", "val", NULL); + testres |= EC_TEST_CHECK(ret == 0, + "cannot set node attribute\n"); + + type = ec_node_type_lookup("seq"); + testres |= EC_TEST_CHECK(type != NULL && + ec_node_check_type(node, type) == 0, + "cannot get seq node type"); + type = ec_node_type_lookup("str"); + testres |= EC_TEST_CHECK(type != NULL && + ec_node_check_type(node, type) < 0, + "node type should not be str"); + + ec_node_free(node); + node = NULL; + + node = ec_node("deznuindez", EC_NO_ID); + testres |= EC_TEST_CHECK(node == NULL, + "should not be able to create node\n"); + + /* test loop */ + expr = ec_node("or", EC_NO_ID); + val = ec_node_int(EC_NO_ID, 0, 10, 0); + op = ec_node_str(EC_NO_ID, "!"); + seq = EC_NODE_SEQ(EC_NO_ID, + op, + ec_node_clone(expr)); + op = NULL; + if (expr == NULL || val == NULL || seq == NULL) + goto fail; + if (ec_node_or_add(expr, ec_node_clone(seq)) < 0) + goto fail; + ec_node_free(seq); + seq = NULL; + if (ec_node_or_add(expr, ec_node_clone(val)) < 0) + goto fail; + ec_node_free(val); + val = NULL; + + testres |= EC_TEST_CHECK_PARSE(expr, 1, "1"); + testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1"); + testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!"); + + ec_node_free(expr); + expr = NULL; + + /* same loop test, but keep some refs (released later) */ + expr = ec_node("or", EC_NO_ID); + ec_node_clone(expr); + expr2 = expr; + val = ec_node_int(EC_NO_ID, 0, 10, 0); + op = ec_node_str(EC_NO_ID, "!"); + seq = EC_NODE_SEQ(EC_NO_ID, + op, + ec_node_clone(expr)); + op = NULL; + if (expr == NULL || val == NULL || seq == NULL) + goto fail; + if (ec_node_or_add(expr, ec_node_clone(seq)) < 0) + goto fail; + ec_node_free(seq); + seq = NULL; + if (ec_node_or_add(expr, ec_node_clone(val)) < 0) + goto fail; + + testres |= EC_TEST_CHECK_PARSE(expr, 1, "1"); + testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1"); + testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!"); + + ec_node_free(expr2); + expr2 = NULL; + ec_node_free(val); + val = NULL; + ec_node_free(expr); + expr = NULL; + + return testres; + +fail: + ec_node_free(expr); + ec_node_free(expr2); + ec_node_free(val); + ec_node_free(seq); + ec_node_free(node); + if (f != NULL) + fclose(f); + free(buf); + + assert(errno != 0); + return -1; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_test = { + .name = "node", + .test = ec_node_testcase, +}; + +EC_TEST_REGISTER(ec_node_test); diff --git a/libecoli/ecoli_node.h b/libecoli/ecoli_node.h new file mode 100644 index 0000000..45f3e74 --- /dev/null +++ b/libecoli/ecoli_node.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 +#include +#include + +#define EC_NO_ID "no-id" + +#define EC_NODE_ENDLIST ((void *)1) + +struct ec_node; +struct ec_parse; +struct ec_comp; +struct ec_strvec; +struct ec_keyval; +struct ec_config; +struct ec_config_schema; + +#define EC_NODE_TYPE_REGISTER(t) \ + static void ec_node_init_##t(void); \ + static void __attribute__((constructor, used)) \ + ec_node_init_##t(void) \ + { \ + if (ec_node_type_register(&t) < 0) \ + fprintf(stderr, \ + "cannot register node type %s\n", \ + t.name); \ + } + +TAILQ_HEAD(ec_node_type_list, ec_node_type); + +typedef int (*ec_node_set_config_t)(struct ec_node *node, + const struct ec_config *config); +typedef int (*ec_node_parse_t)(const struct ec_node *node, + struct ec_parse *state, + const struct ec_strvec *strvec); +typedef int (*ec_node_complete_t)(const struct ec_node *node, + struct ec_comp *comp_state, + const struct ec_strvec *strvec); +typedef const char * (*ec_node_desc_t)(const struct ec_node *); +typedef int (*ec_node_init_priv_t)(struct ec_node *); +typedef void (*ec_node_free_priv_t)(struct ec_node *); +typedef size_t (*ec_node_get_children_count_t)(const struct ec_node *); +typedef int (*ec_node_get_child_t)(const struct ec_node *, + size_t i, struct ec_node **child, unsigned int *refs); + +/** + * A structure describing a node type. + */ +struct ec_node_type { + TAILQ_ENTRY(ec_node_type) next; /**< Next in list. */ + const char *name; /**< Node type name. */ + /** Configuration schema array, must be terminated by a sentinel + * (.type = EC_CONFIG_TYPE_NONE). */ + const struct ec_config_schema *schema; + ec_node_set_config_t set_config; /* validate/ack a config change */ + ec_node_parse_t parse; + ec_node_complete_t complete; + ec_node_desc_t desc; + size_t size; + ec_node_init_priv_t init_priv; + ec_node_free_priv_t free_priv; + ec_node_get_children_count_t get_children_count; + ec_node_get_child_t get_child; +}; + +/** + * Register a node type. + * + * @param type + * A pointer to a ec_test structure describing the test + * to be registered. + * @return + * 0 on success, negative value on error. + */ +int ec_node_type_register(struct ec_node_type *type); + +/** + * Lookup node type by name + * + * @param name + * The name of the node type to search. + * @return + * The node type if found, or NULL on error. + */ +const struct ec_node_type *ec_node_type_lookup(const char *name); + +/** + * Dump registered log types + */ +void ec_node_type_dump(FILE *out); + +/** + * Get the config schema of a node type. + */ +const struct ec_config_schema * +ec_node_type_schema(const struct ec_node_type *type); + +/** + * Get the name of a node type. + */ +const char * +ec_node_type_name(const struct ec_node_type *type); + +enum ec_node_free_state { + EC_NODE_FREE_STATE_NONE, + EC_NODE_FREE_STATE_TRAVERSED, + EC_NODE_FREE_STATE_FREEABLE, + EC_NODE_FREE_STATE_NOT_FREEABLE, + EC_NODE_FREE_STATE_FREEING, +}; + +struct ec_node { + const struct ec_node_type *type; + struct ec_config *config; /**< Generic configuration. */ + char *id; + char *desc; + struct ec_keyval *attrs; + unsigned int refcnt; + struct { + enum ec_node_free_state state; /**< State of loop detection */ + unsigned int refcnt; /**< Number of reachable references + * starting from node beeing freed */ + } free; /**< Freeing state: used for loop detection */ +}; + +/* create a new node when the type is known, typically called from the node + * code */ +struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id); + +/* create a new node */ +struct ec_node *ec_node(const char *typename, const char *id); + +struct ec_node *ec_node_clone(struct ec_node *node); +void ec_node_free(struct ec_node *node); + +/* set configuration of a node + * after a call to this function, the config is + * owned by the node and must not be used by the caller + * on error, the config is freed. */ +int ec_node_set_config(struct ec_node *node, struct ec_config *config); + +/* get the current node configuration. Return NULL if no configuration. */ +const struct ec_config *ec_node_get_config(struct ec_node *node); + +size_t ec_node_get_children_count(const struct ec_node *node); +int +ec_node_get_child(const struct ec_node *node, size_t i, + struct ec_node **child, unsigned int *refs); + +/* XXX add more accessors */ +const struct ec_node_type *ec_node_type(const struct ec_node *node); +struct ec_keyval *ec_node_attrs(const struct ec_node *node); +const char *ec_node_id(const struct ec_node *node); +const char *ec_node_desc(const struct ec_node *node); + +void ec_node_dump(FILE *out, const struct ec_node *node); +struct ec_node *ec_node_find(struct ec_node *node, const char *id); + +/* check the type of a node */ +int ec_node_check_type(const struct ec_node *node, + const struct ec_node_type *type); + +#endif diff --git a/libecoli/ecoli_node_any.c b/libecoli/ecoli_node_any.c new file mode 100644 index 0000000..197a2c5 --- /dev/null +++ b/libecoli/ecoli_node_any.c @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_any); + +struct ec_node_any { + struct ec_node gen; +}; + +static int ec_node_any_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + (void)gen_node; + (void)state; + + if (ec_strvec_len(strvec) == 0) + return EC_PARSE_NOMATCH; + + return 1; +} + +static struct ec_node_type ec_node_any_type = { + .name = "any", + .parse = ec_node_any_parse, + .complete = ec_node_complete_unknown, + .size = sizeof(struct ec_node_any), +}; + +EC_NODE_TYPE_REGISTER(ec_node_any_type); + +/* LCOV_EXCL_START */ +static int ec_node_any_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node("any", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1); + ec_node_free(node); + + /* never completes */ + node = ec_node("any", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_any_test = { + .name = "node_any", + .test = ec_node_any_testcase, +}; + +EC_TEST_REGISTER(ec_node_any_test); diff --git a/libecoli/ecoli_node_any.h b/libecoli/ecoli_node_any.h new file mode 100644 index 0000000..ee638aa --- /dev/null +++ b/libecoli/ecoli_node_any.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * This node always matches 1 string in the vector + */ + +#ifndef ECOLI_NODE_ANY_ +#define ECOLI_NODE_ANY_ + +/* no specific API for this node */ + +#endif diff --git a/libecoli/ecoli_node_cmd.c b/libecoli/ecoli_node_cmd.c new file mode 100644 index 0000000..c61b759 --- /dev/null +++ b/libecoli/ecoli_node_cmd.c @@ -0,0 +1,682 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_cmd); + +struct ec_node_cmd { + struct ec_node gen; + char *cmd_str; /* the command string. */ + struct ec_node *cmd; /* the command node. */ + struct ec_node *parser; /* the expression parser. */ + struct ec_node *expr; /* the expression parser without lexer. */ + struct ec_node **table; /* table of node referenced in command. */ + unsigned int len; /* len of the table. */ +}; + +/* passed as user context to expression parser */ +struct ec_node_cmd_ctx { + struct ec_node **table; + unsigned int len; +}; + +static int +ec_node_cmd_eval_var(void **result, void *userctx, + const struct ec_parse *var) +{ + const struct ec_strvec *vec; + struct ec_node_cmd_ctx *ctx = userctx; + struct ec_node *eval = NULL; + const char *str, *id; + unsigned int i; + + /* get parsed string vector, it should contain only one str */ + vec = ec_parse_strvec(var); + if (ec_strvec_len(vec) != 1) { + errno = EINVAL; + return -1; + } + str = ec_strvec_val(vec, 0); + + for (i = 0; i < ctx->len; i++) { + id = ec_node_id(ctx->table[i]); + if (id == NULL) + continue; + if (strcmp(str, id)) + continue; + /* if id matches, use a node provided by the user... */ + eval = ec_node_clone(ctx->table[i]); + if (eval == NULL) + return -1; + break; + } + + /* ...or create a string node */ + if (eval == NULL) { + eval = ec_node_str(EC_NO_ID, str); + if (eval == NULL) + return -1; + } + + *result = eval; + + return 0; +} + +static int +ec_node_cmd_eval_pre_op(void **result, void *userctx, void *operand, + const struct ec_parse *operator) +{ + (void)result; + (void)userctx; + (void)operand; + (void)operator; + + errno = EINVAL; + return -1; +} + +static int +ec_node_cmd_eval_post_op(void **result, void *userctx, void *operand, + const struct ec_parse *operator) +{ + const struct ec_strvec *vec; + struct ec_node *in = operand;; + struct ec_node *out = NULL;; + + (void)userctx; + + /* get parsed string vector, it should contain only one str */ + vec = ec_parse_strvec(operator); + if (ec_strvec_len(vec) != 1) { + errno = EINVAL; + return -1; + } + + if (!strcmp(ec_strvec_val(vec, 0), "*")) { + out = ec_node_many(EC_NO_ID, + ec_node_clone(in), 0, 0); + if (out == NULL) + return -1; + ec_node_free(in); + *result = out; + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +static int +ec_node_cmd_eval_bin_op(void **result, void *userctx, void *operand1, + const struct ec_parse *operator, void *operand2) + +{ + const struct ec_strvec *vec; + struct ec_node *out = NULL; + struct ec_node *in1 = operand1; + struct ec_node *in2 = operand2; + + (void)userctx; + + /* get parsed string vector, it should contain only one str */ + vec = ec_parse_strvec(operator); + if (ec_strvec_len(vec) > 1) { + errno = EINVAL; + return -1; + } + + if (ec_strvec_len(vec) == 0) { + if (!strcmp(in1->type->name, "seq")) { + if (ec_node_seq_add(in1, ec_node_clone(in2)) < 0) + return -1; + ec_node_free(in2); + *result = in1; + } else { + out = EC_NODE_SEQ(EC_NO_ID, ec_node_clone(in1), + ec_node_clone(in2)); + if (out == NULL) + return -1; + ec_node_free(in1); + ec_node_free(in2); + *result = out; + } + } else if (!strcmp(ec_strvec_val(vec, 0), "|")) { + if (!strcmp(in2->type->name, "or")) { + if (ec_node_or_add(in2, ec_node_clone(in1)) < 0) + return -1; + ec_node_free(in1); + *result = in2; + } else if (!strcmp(in1->type->name, "or")) { + if (ec_node_or_add(in1, ec_node_clone(in2)) < 0) + return -1; + ec_node_free(in2); + *result = in1; + } else { + out = EC_NODE_OR(EC_NO_ID, ec_node_clone(in1), + ec_node_clone(in2)); + if (out == NULL) + return -1; + ec_node_free(in1); + ec_node_free(in2); + *result = out; + } + } else if (!strcmp(ec_strvec_val(vec, 0), ",")) { + if (!strcmp(in2->type->name, "subset")) { + if (ec_node_subset_add(in2, ec_node_clone(in1)) < 0) + return -1; + ec_node_free(in1); + *result = in2; + } else if (!strcmp(in1->type->name, "subset")) { + if (ec_node_subset_add(in1, ec_node_clone(in2)) < 0) + return -1; + ec_node_free(in2); + *result = in1; + } else { + out = EC_NODE_SUBSET(EC_NO_ID, ec_node_clone(in1), + ec_node_clone(in2)); + if (out == NULL) + return -1; + ec_node_free(in1); + ec_node_free(in2); + *result = out; + } + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +static int +ec_node_cmd_eval_parenthesis(void **result, void *userctx, + const struct ec_parse *open_paren, + const struct ec_parse *close_paren, + void *value) +{ + const struct ec_strvec *vec; + struct ec_node *in = value;; + struct ec_node *out = NULL;; + + (void)userctx; + (void)close_paren; + + /* get parsed string vector, it should contain only one str */ + vec = ec_parse_strvec(open_paren); + if (ec_strvec_len(vec) != 1) { + errno = EINVAL; + return -1; + } + + if (!strcmp(ec_strvec_val(vec, 0), "[")) { + out = ec_node_option(EC_NO_ID, ec_node_clone(in)); + if (out == NULL) + return -1; + ec_node_free(in); + } else if (!strcmp(ec_strvec_val(vec, 0), "(")) { + out = in; + } else { + errno = EINVAL; + return -1; + } + + *result = out; + + return 0; +} + +static void +ec_node_cmd_eval_free(void *result, void *userctx) +{ + (void)userctx; + ec_free(result); +} + +static const struct ec_node_expr_eval_ops expr_ops = { + .eval_var = ec_node_cmd_eval_var, + .eval_pre_op = ec_node_cmd_eval_pre_op, + .eval_post_op = ec_node_cmd_eval_post_op, + .eval_bin_op = ec_node_cmd_eval_bin_op, + .eval_parenthesis = ec_node_cmd_eval_parenthesis, + .eval_free = ec_node_cmd_eval_free, +}; + +static struct ec_node * +ec_node_cmd_build_expr(void) +{ + struct ec_node *expr = NULL; + int ret; + + /* build the expression parser */ + expr = ec_node("expr", "expr"); + if (expr == NULL) + goto fail; + ret = ec_node_expr_set_val_node(expr, ec_node_re(EC_NO_ID, + "[a-zA-Z0-9]+")); + if (ret < 0) + goto fail; + ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, ",")); + if (ret < 0) + goto fail; + ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, "|")); + if (ret < 0) + goto fail; + ret = ec_node_expr_add_bin_op(expr, ec_node("empty", EC_NO_ID)); + if (ret < 0) + goto fail; + ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "+")); + if (ret < 0) + goto fail; + ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "*")); + if (ret < 0) + goto fail; + ret = ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "["), + ec_node_str(EC_NO_ID, "]")); + if (ret < 0) + goto fail; + ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "("), + ec_node_str(EC_NO_ID, ")")); + if (ret < 0) + goto fail; + + return expr; + +fail: + ec_node_free(expr); + return NULL; +} + +static struct ec_node * +ec_node_cmd_build_parser(struct ec_node *expr) +{ + struct ec_node *lex = NULL; + int ret; + + /* prepend a lexer to the expression node */ + lex = ec_node_re_lex(EC_NO_ID, ec_node_clone(expr)); + if (lex == NULL) + goto fail; + + ret = ec_node_re_lex_add(lex, "[a-zA-Z0-9]+", 1); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "[*|,()]", 1); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "\\[", 1); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "\\]", 1); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "[ ]+", 0); + if (ret < 0) + goto fail; + + return lex; + +fail: + ec_node_free(lex); + + return NULL; +} + +static struct ec_node * +ec_node_cmd_build(struct ec_node_cmd *node, const char *cmd_str, + struct ec_node **table, size_t len) +{ + struct ec_node_cmd_ctx ctx = { table, len }; + struct ec_parse *p = NULL; + void *result; + int ret; + + /* parse the command expression */ + p = ec_node_parse(node->parser, cmd_str); + if (p == NULL) + goto fail; + + if (!ec_parse_matches(p)) { + errno = EINVAL; + goto fail; + } + if (!ec_parse_has_child(p)) { + errno = EINVAL; + goto fail; + } + + ret = ec_node_expr_eval(&result, node->expr, + ec_parse_get_first_child(p), + &expr_ops, &ctx); + if (ret < 0) + goto fail; + + ec_parse_free(p); + return result; + +fail: + ec_parse_free(p); + return NULL; +} + +static int +ec_node_cmd_parse(const struct ec_node *gen_node, struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; + + return ec_node_parse_child(node->cmd, state, strvec); +} + +static int +ec_node_cmd_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; + + return ec_node_complete_child(node->cmd, comp, strvec); +} + +static void ec_node_cmd_free_priv(struct ec_node *gen_node) +{ + struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; + size_t i; + + ec_free(node->cmd_str); + node->cmd_str = NULL; + ec_node_free(node->expr); + node->expr = NULL; + ec_node_free(node->parser); + node->parser = NULL; + ec_node_free(node->cmd); + node->cmd = NULL; + for (i = 0; i < node->len; i++) + ec_node_free(node->table[i]); + ec_free(node->table); + node->table = NULL; + node->len = 0; +} + +static const struct ec_config_schema ec_node_cmd_subschema[] = { + { + .desc = "A child node whose id is referenced in the expression.", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static const struct ec_config_schema ec_node_cmd_schema[] = { + { + .key = "expr", + .desc = "The expression to match. Supported operators " + "are or '|', list ',', many '+', many-or-zero '*', " + "option '[]', group '()'. An identifier (alphanumeric) can " + "reference a node whose node_id matches. Else it is " + "interpreted as ec_node_str() matching this string. " + "Example: command [option] (subset1, subset2) x|y", + .type = EC_CONFIG_TYPE_STRING, + }, + { + .key = "children", + .desc = "The list of children nodes.", + .type = EC_CONFIG_TYPE_LIST, + .subschema = ec_node_cmd_subschema, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_cmd_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; + const struct ec_config *expr = NULL; + struct ec_node *cmd = NULL; + struct ec_node **table = NULL; + char *cmd_str = NULL; + size_t len = 0, i; + + /* retrieve config locally */ + expr = ec_config_dict_get(config, "expr"); + if (expr == NULL) { + errno = EINVAL; + goto fail; + } + + table = ec_node_config_node_list_to_table( + ec_config_dict_get(config, "children"), &len); + if (table == NULL) + goto fail; + + cmd_str = ec_strdup(expr->string); + if (cmd_str == NULL) + goto fail; + + /* parse expression to build the cmd child node */ + cmd = ec_node_cmd_build(node, cmd_str, table, len); + if (cmd == NULL) + goto fail; + + /* ok, store the config */ + ec_node_free(node->cmd); + node->cmd = cmd; + ec_free(node->cmd_str); + node->cmd_str = cmd_str; + for (i = 0; i < node->len; i++) + ec_node_free(node->table[i]); + ec_free(node->table); + node->table = table; + node->len = len; + + return 0; + +fail: + for (i = 0; i < len; i++) + ec_node_free(table[i]); + ec_free(table); + ec_free(cmd_str); + ec_node_free(cmd); + return -1; +} + +static size_t +ec_node_cmd_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; + + if (node->cmd == NULL) + return 0; + return 1; +} + +static int +ec_node_cmd_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node; + + if (i > 0) + return -1; + + *child = node->cmd; + *refs = 1; + return 0; +} + +static struct ec_node_type ec_node_cmd_type = { + .name = "cmd", + .schema = ec_node_cmd_schema, + .set_config = ec_node_cmd_set_config, + .parse = ec_node_cmd_parse, + .complete = ec_node_cmd_complete, + .size = sizeof(struct ec_node_cmd), + .free_priv = ec_node_cmd_free_priv, + .get_children_count = ec_node_cmd_get_children_count, + .get_child = ec_node_cmd_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_cmd_type); + +struct ec_node *__ec_node_cmd(const char *id, const char *cmd, ...) +{ + struct ec_config *config = NULL, *children = NULL; + struct ec_node *gen_node = NULL; + struct ec_node_cmd *node = NULL; + va_list ap; + int ret; + + /* this block must stay first, it frees the nodes on error */ + va_start(ap, cmd); + children = ec_node_config_node_list_from_vargs(ap); + va_end(ap); + if (children == NULL) + goto fail; + + gen_node = ec_node_from_type(&ec_node_cmd_type, id); + if (gen_node == NULL) + goto fail; + node = (struct ec_node_cmd *)gen_node; + + node->expr = ec_node_cmd_build_expr(); + if (node->expr == NULL) + goto fail; + + node->parser = ec_node_cmd_build_parser(node->expr); + if (node->parser == NULL) + goto fail; + + config = ec_config_dict(); + if (config == NULL) + goto fail; + + if (ec_config_dict_set(config, "expr", ec_config_string(cmd)) < 0) + goto fail; + + if (ec_config_dict_set(config, "children", children) < 0) { + children = NULL; /* freed */ + goto fail; + } + children = NULL; + + ret = ec_node_set_config(gen_node, config); + config = NULL; /* freed */ + if (ret < 0) + goto fail; + + return gen_node; + +fail: + ec_node_free(gen_node); /* will also free added children */ + ec_config_free(children); + ec_config_free(config); + + return NULL; +} + +/* LCOV_EXCL_START */ +static int ec_node_cmd_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = EC_NODE_CMD(EC_NO_ID, + "command [option] (subset1, subset2, subset3, subset4) x|y z*", + ec_node_int("x", 0, 10, 10), + ec_node_int("y", 20, 30, 10) + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "subset1", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 4, "command", "subset3", "subset2", + "1"); + testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "subset2", "subset3", + "subset1", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 6, "command", "subset3", "subset1", + "subset4", "subset2", "4"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "23"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "option", "23"); + testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "option", "23", + "z", "z"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "command", "15"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); + ec_node_free(node); + + node = EC_NODE_CMD(EC_NO_ID, "good morning [count] bob|bobby|michael", + ec_node_int("count", 0, 10, 10)); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 4, "good", "morning", "1", "bob"); + + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "good", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "g", EC_NODE_ENDLIST, + "good", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "good", "morning", "", EC_NODE_ENDLIST, + "bob", "bobby", "michael", EC_NODE_ENDLIST); + + ec_node_free(node); + + node = EC_NODE_CMD(EC_NO_ID, "[foo [bar]]"); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 0); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 0, "x"); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_cmd_test = { + .name = "node_cmd", + .test = ec_node_cmd_testcase, +}; + +EC_TEST_REGISTER(ec_node_cmd_test); diff --git a/libecoli/ecoli_node_cmd.h b/libecoli/ecoli_node_cmd.h new file mode 100644 index 0000000..99afc01 --- /dev/null +++ b/libecoli/ecoli_node_cmd.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_CMD_ +#define ECOLI_NODE_CMD_ + +#include + +#define EC_NODE_CMD(args...) __ec_node_cmd(args, EC_NODE_ENDLIST) + +struct ec_node *__ec_node_cmd(const char *id, const char *cmd_str, ...); + +#endif diff --git a/libecoli/ecoli_node_dynamic.c b/libecoli/ecoli_node_dynamic.c new file mode 100644 index 0000000..8a3edf3 --- /dev/null +++ b/libecoli/ecoli_node_dynamic.c @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2017, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ecoli_node_dynamic.h" + +EC_LOG_TYPE_REGISTER(node_dynamic); + +struct ec_node_dynamic { + struct ec_node gen; + ec_node_dynamic_build_t build; + void *opaque; +}; + +static int +ec_node_dynamic_parse(const struct ec_node *gen_node, + struct ec_parse *parse, + const struct ec_strvec *strvec) +{ + struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node; + struct ec_node *child = NULL; + void (*node_free)(struct ec_node *) = ec_node_free; + char key[64]; + int ret = -1; + + child = node->build(parse, node->opaque); + if (child == NULL) + goto fail; + + /* add the node pointer in the attributes, so it will be freed + * when parse is freed */ + snprintf(key, sizeof(key), "_dyn_%p", child); + ret = ec_keyval_set(ec_parse_get_attrs(parse), key, child, + (void *)node_free); + if (ret < 0) { + child = NULL; /* already freed */ + goto fail; + } + + return ec_node_parse_child(child, parse, strvec); + +fail: + ec_node_free(child); + return ret; +} + +static int +ec_node_dynamic_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node; + struct ec_parse *parse; + struct ec_node *child = NULL; + void (*node_free)(struct ec_node *) = ec_node_free; + char key[64]; + int ret = -1; + + parse = ec_comp_get_state(comp); + child = node->build(parse, node->opaque); + if (child == NULL) + goto fail; + + /* add the node pointer in the attributes, so it will be freed + * when parse is freed */ + snprintf(key, sizeof(key), "_dyn_%p", child); + ret = ec_keyval_set(comp->attrs, key, child, + (void *)node_free); + if (ret < 0) { + child = NULL; /* already freed */ + goto fail; + } + + return ec_node_complete_child(child, comp, strvec); + +fail: + ec_node_free(child); + return ret; +} + +static struct ec_node_type ec_node_dynamic_type = { + .name = "dynamic", + .parse = ec_node_dynamic_parse, + .complete = ec_node_dynamic_complete, + .size = sizeof(struct ec_node_dynamic), +}; + +struct ec_node * +ec_node_dynamic(const char *id, ec_node_dynamic_build_t build, void *opaque) +{ + struct ec_node *gen_node = NULL; + struct ec_node_dynamic *node; + + if (build == NULL) { + errno = EINVAL; + goto fail; + } + + gen_node = ec_node_from_type(&ec_node_dynamic_type, id); + if (gen_node == NULL) + goto fail; + + node = (struct ec_node_dynamic *)gen_node; + node->build = build; + node->opaque = opaque; + + return gen_node; + +fail: + ec_node_free(gen_node); + return NULL; + +} + +EC_NODE_TYPE_REGISTER(ec_node_dynamic_type); + +static struct ec_node * +build_counter(struct ec_parse *parse, void *opaque) +{ + const struct ec_node *node; + struct ec_parse *iter; + unsigned int count = 0; + char buf[32]; + + (void)opaque; + for (iter = ec_parse_get_root(parse); iter != NULL; + iter = ec_parse_iter_next(iter)) { + node = ec_parse_get_node(iter); + if (node->id && !strcmp(node->id, "my-id")) + count++; + } + snprintf(buf, sizeof(buf), "count-%u", count); + + return ec_node_str("my-id", buf); +} + +/* LCOV_EXCL_START */ +static int ec_node_dynamic_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node_many(EC_NO_ID, + ec_node_dynamic(EC_NO_ID, build_counter, NULL), + 1, 3); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "count-0", "count-1", "count-2"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0", "count-0"); + + /* test completion */ + + testres |= EC_TEST_CHECK_COMPLETE(node, + "c", EC_NODE_ENDLIST, + "count-0", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "count-0", "", EC_NODE_ENDLIST, + "count-1", EC_NODE_ENDLIST, + "count-1"); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_dynamic_test = { + .name = "node_dynamic", + .test = ec_node_dynamic_testcase, +}; + +EC_TEST_REGISTER(ec_node_dynamic_test); diff --git a/libecoli/ecoli_node_dynamic.h b/libecoli/ecoli_node_dynamic.h new file mode 100644 index 0000000..4f2535e --- /dev/null +++ b/libecoli/ecoli_node_dynamic.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2017, Olivier MATZ + */ + +#ifndef ECOLI_NODE_DYNAMIC_ +#define ECOLI_NODE_DYNAMIC_ + +struct ec_node; +struct ec_parse; + +/* callback invoked by parse() or complete() to build the dynamic node + * the behavior of the node can depend on what is already parsed */ +typedef struct ec_node *(*ec_node_dynamic_build_t)( + struct ec_parse *state, void *opaque); + +struct ec_node *ec_node_dynamic(const char *id, ec_node_dynamic_build_t build, + void *opaque); + +#endif diff --git a/libecoli/ecoli_node_empty.c b/libecoli/ecoli_node_empty.c new file mode 100644 index 0000000..6ce76e8 --- /dev/null +++ b/libecoli/ecoli_node_empty.c @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_empty); + +struct ec_node_empty { + struct ec_node gen; +}; + +static int ec_node_empty_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + (void)gen_node; + (void)state; + (void)strvec; + return 0; +} + +static struct ec_node_type ec_node_empty_type = { + .name = "empty", + .parse = ec_node_empty_parse, + .complete = ec_node_complete_unknown, + .size = sizeof(struct ec_node_empty), +}; + +EC_NODE_TYPE_REGISTER(ec_node_empty_type); + +/* LCOV_EXCL_START */ +static int ec_node_empty_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node("empty", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 0, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 0); + testres |= EC_TEST_CHECK_PARSE(node, 0, "foo", "bar"); + ec_node_free(node); + + /* never completes */ + node = ec_node("empty", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_empty_test = { + .name = "node_empty", + .test = ec_node_empty_testcase, +}; + +EC_TEST_REGISTER(ec_node_empty_test); diff --git a/libecoli/ecoli_node_empty.h b/libecoli/ecoli_node_empty.h new file mode 100644 index 0000000..ed5e32e --- /dev/null +++ b/libecoli/ecoli_node_empty.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * This node always matches an empty string vector + */ + +#ifndef ECOLI_NODE_EMPTY_ +#define ECOLI_NODE_EMPTY_ + +struct ec_node *ec_node_empty(const char *id); + +#endif diff --git a/libecoli/ecoli_node_expr.c b/libecoli/ecoli_node_expr.c new file mode 100644 index 0000000..3b47d8c --- /dev/null +++ b/libecoli/ecoli_node_expr.c @@ -0,0 +1,617 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_expr); + +struct ec_node_expr { + struct ec_node gen; + + /* the built node */ + struct ec_node *child; + + /* the configuration nodes */ + struct ec_node *val_node; + struct ec_node **bin_ops; + unsigned int bin_ops_len; + struct ec_node **pre_ops; + unsigned int pre_ops_len; + struct ec_node **post_ops; + unsigned int post_ops_len; + struct ec_node **open_ops; + struct ec_node **close_ops; + unsigned int paren_len; +}; + +static int ec_node_expr_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + + if (node->child == NULL) { + errno = ENOENT; + return -1; + } + + return ec_node_parse_child(node->child, state, strvec); +} + +static int +ec_node_expr_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + + if (node->child == NULL) { + errno = ENOENT; + return -1; + } + + return ec_node_complete_child(node->child, comp, strvec); +} + +static void ec_node_expr_free_priv(struct ec_node *gen_node) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + unsigned int i; + + ec_node_free(node->child); + ec_node_free(node->val_node); + + for (i = 0; i < node->bin_ops_len; i++) + ec_node_free(node->bin_ops[i]); + ec_free(node->bin_ops); + for (i = 0; i < node->pre_ops_len; i++) + ec_node_free(node->pre_ops[i]); + ec_free(node->pre_ops); + for (i = 0; i < node->post_ops_len; i++) + ec_node_free(node->post_ops[i]); + ec_free(node->post_ops); + for (i = 0; i < node->paren_len; i++) { + ec_node_free(node->open_ops[i]); + ec_node_free(node->close_ops[i]); + } + ec_free(node->open_ops); + ec_free(node->close_ops); +} + +static int ec_node_expr_build(struct ec_node_expr *node) +{ + struct ec_node *term = NULL, *expr = NULL, *next = NULL, + *pre_op = NULL, *post_op = NULL, *ref = NULL, + *post = NULL; + unsigned int i; + + ec_node_free(node->child); + node->child = NULL; + + if (node->val_node == NULL) { + errno = EINVAL; + return -1; + } + + if (node->bin_ops_len == 0 && node->pre_ops_len == 0 && + node->post_ops_len == 0) { + errno = EINVAL; + return -1; + } + + /* + * Example of created grammar: + * + * pre_op = "!" + * post_op = "^" + * post = val | + * pre_op expr | + * "(" expr ")" + * term = post post_op* + * prod = term ( "*" term )* + * sum = prod ( "+" prod )* + * expr = sum + */ + + /* we use this as a ref, will be set later */ + ref = ec_node("seq", "ref"); + if (ref == NULL) + return -1; + + /* prefix unary operators */ + pre_op = ec_node("or", "pre-op"); + if (pre_op == NULL) + goto fail; + for (i = 0; i < node->pre_ops_len; i++) { + if (ec_node_or_add(pre_op, ec_node_clone(node->pre_ops[i])) < 0) + goto fail; + } + + /* suffix unary operators */ + post_op = ec_node("or", "post-op"); + if (post_op == NULL) + goto fail; + for (i = 0; i < node->post_ops_len; i++) { + if (ec_node_or_add(post_op, ec_node_clone(node->post_ops[i])) < 0) + goto fail; + } + + post = ec_node("or", "post"); + if (post == NULL) + goto fail; + if (ec_node_or_add(post, ec_node_clone(node->val_node)) < 0) + goto fail; + if (ec_node_or_add(post, + EC_NODE_SEQ(EC_NO_ID, + ec_node_clone(pre_op), + ec_node_clone(ref))) < 0) + goto fail; + for (i = 0; i < node->paren_len; i++) { + if (ec_node_or_add(post, EC_NODE_SEQ(EC_NO_ID, + ec_node_clone(node->open_ops[i]), + ec_node_clone(ref), + ec_node_clone(node->close_ops[i]))) < 0) + goto fail; + } + term = EC_NODE_SEQ("term", + ec_node_clone(post), + ec_node_many(EC_NO_ID, ec_node_clone(post_op), 0, 0) + ); + if (term == NULL) + goto fail; + + for (i = 0; i < node->bin_ops_len; i++) { + next = EC_NODE_SEQ("next", + ec_node_clone(term), + ec_node_many(EC_NO_ID, + EC_NODE_SEQ(EC_NO_ID, + ec_node_clone(node->bin_ops[i]), + ec_node_clone(term) + ), + 0, 0 + ) + ); + ec_node_free(term); + term = next; + if (term == NULL) + goto fail; + } + expr = term; + term = NULL; + + /* free the initial references */ + ec_node_free(pre_op); + pre_op = NULL; + ec_node_free(post_op); + post_op = NULL; + ec_node_free(post); + post = NULL; + + if (ec_node_seq_add(ref, ec_node_clone(expr)) < 0) + goto fail; + ec_node_free(ref); + ref = NULL; + + node->child = expr; + + return 0; + +fail: + ec_node_free(term); + ec_node_free(expr); + ec_node_free(pre_op); + ec_node_free(post_op); + ec_node_free(post); + ec_node_free(ref); + + return -1; +} + +static size_t +ec_node_expr_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + + if (node->child) + return 1; + return 0; +} + +static int +ec_node_expr_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + + if (i >= 1) + return -1; + + *child = node->child; + *refs = 1; + return 0; +} + +static struct ec_node_type ec_node_expr_type = { + .name = "expr", + .parse = ec_node_expr_parse, + .complete = ec_node_expr_complete, + .size = sizeof(struct ec_node_expr), + .free_priv = ec_node_expr_free_priv, + .get_children_count = ec_node_expr_get_children_count, + .get_child = ec_node_expr_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_expr_type); + +int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + + if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) + goto fail; + + if (val_node == NULL) { + errno = EINVAL; + goto fail; + } + + ec_node_free(node->val_node); + node->val_node = val_node; + ec_node_expr_build(node); + + return 0; + +fail: + ec_node_free(val_node); + return -1; +} + +/* add a binary operator */ +int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + struct ec_node **bin_ops; + + if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) + goto fail; + + if (node == NULL || op == NULL) { + errno = EINVAL; + goto fail; + } + + bin_ops = ec_realloc(node->bin_ops, + (node->bin_ops_len + 1) * sizeof(*node->bin_ops)); + if (bin_ops == NULL) + goto fail;; + + node->bin_ops = bin_ops; + bin_ops[node->bin_ops_len] = op; + node->bin_ops_len++; + ec_node_expr_build(node); + + return 0; + +fail: + ec_node_free(op); + return -1; +} + +/* add a unary pre-operator */ +int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + struct ec_node **pre_ops; + + if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) + goto fail; + + if (node == NULL || op == NULL) { + errno = EINVAL; + goto fail; + } + + pre_ops = ec_realloc(node->pre_ops, + (node->pre_ops_len + 1) * sizeof(*node->pre_ops)); + if (pre_ops == NULL) + goto fail; + + node->pre_ops = pre_ops; + pre_ops[node->pre_ops_len] = op; + node->pre_ops_len++; + ec_node_expr_build(node); + + return 0; + +fail: + ec_node_free(op); + return -1; +} + +/* add a unary post-operator */ +int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + struct ec_node **post_ops; + + if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) + goto fail; + + if (node == NULL || op == NULL) { + errno = EINVAL; + goto fail; + } + + post_ops = ec_realloc(node->post_ops, + (node->post_ops_len + 1) * sizeof(*node->post_ops)); + if (post_ops == NULL) + goto fail; + + node->post_ops = post_ops; + post_ops[node->post_ops_len] = op; + node->post_ops_len++; + ec_node_expr_build(node); + + return 0; + +fail: + ec_node_free(op); + return -1; +} + +/* add parenthesis symbols */ +int ec_node_expr_add_parenthesis(struct ec_node *gen_node, + struct ec_node *open, struct ec_node *close) +{ + struct ec_node_expr *node = (struct ec_node_expr *)gen_node; + struct ec_node **open_ops, **close_ops; + + if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0) + goto fail; + + if (node == NULL || open == NULL || close == NULL) { + errno = EINVAL; + goto fail; + } + + open_ops = ec_realloc(node->open_ops, + (node->paren_len + 1) * sizeof(*node->open_ops)); + if (open_ops == NULL) + goto fail; + close_ops = ec_realloc(node->close_ops, + (node->paren_len + 1) * sizeof(*node->close_ops)); + if (close_ops == NULL) + goto fail; + + node->open_ops = open_ops; + node->close_ops = close_ops; + open_ops[node->paren_len] = open; + close_ops[node->paren_len] = close; + node->paren_len++; + ec_node_expr_build(node); + + return 0; + +fail: + ec_node_free(open); + ec_node_free(close); + return -1; +} + +enum expr_node_type { + NONE, + VAL, + BIN_OP, + PRE_OP, + POST_OP, + PAREN_OPEN, + PAREN_CLOSE, +}; + +static enum expr_node_type get_node_type(const struct ec_node *expr_gen_node, + const struct ec_node *check) +{ + struct ec_node_expr *expr_node = (struct ec_node_expr *)expr_gen_node; + size_t i; + + if (check == expr_node->val_node) + return VAL; + + for (i = 0; i < expr_node->bin_ops_len; i++) { + if (check == expr_node->bin_ops[i]) + return BIN_OP; + } + for (i = 0; i < expr_node->pre_ops_len; i++) { + if (check == expr_node->pre_ops[i]) + return PRE_OP; + } + for (i = 0; i < expr_node->post_ops_len; i++) { + if (check == expr_node->post_ops[i]) + return POST_OP; + } + + for (i = 0; i < expr_node->paren_len; i++) { + if (check == expr_node->open_ops[i]) + return PAREN_OPEN; + } + for (i = 0; i < expr_node->paren_len; i++) { + if (check == expr_node->close_ops[i]) + return PAREN_CLOSE; + } + + return NONE; +} + +struct result { + bool has_val; + void *val; + const struct ec_parse *op; + enum expr_node_type op_type; +}; + +/* merge x and y results in x */ +static int merge_results(void *userctx, + const struct ec_node_expr_eval_ops *ops, + struct result *x, const struct result *y) +{ + if (y->has_val == 0 && y->op == NULL) + return 0; + if (x->has_val == 0 && x->op == NULL) { + *x = *y; + return 0; + } + + if (x->has_val && y->has_val && y->op != NULL) { + if (y->op_type == BIN_OP) { + if (ops->eval_bin_op(&x->val, userctx, x->val, + y->op, y->val) < 0) + return -1; + + return 0; + } + } + + if (x->has_val == 0 && x->op != NULL && y->has_val && y->op == NULL) { + if (x->op_type == PRE_OP) { + if (ops->eval_pre_op(&x->val, userctx, y->val, + x->op) < 0) + return -1; + x->has_val = true; + x->op_type = NONE; + x->op = NULL; + return 0; + } else if (x->op_type == BIN_OP) { + x->val = y->val; + x->has_val = true; + return 0; + } + } + + if (x->has_val && x->op == NULL && y->has_val == 0 && y->op != NULL) { + if (ops->eval_post_op(&x->val, userctx, x->val, y->op) < 0) + return -1; + + return 0; + } + + assert(false); /* we should not get here */ + return -1; +} + +static int eval_expression(struct result *result, + void *userctx, + const struct ec_node_expr_eval_ops *ops, + const struct ec_node *expr_gen_node, + const struct ec_parse *parse) + +{ + struct ec_parse *open = NULL, *close = NULL; + struct result child_result; + struct ec_parse *child; + enum expr_node_type type; + + memset(result, 0, sizeof(*result)); + memset(&child_result, 0, sizeof(child_result)); + + type = get_node_type(expr_gen_node, ec_parse_get_node(parse)); + if (type == VAL) { + if (ops->eval_var(&result->val, userctx, parse) < 0) + goto fail; + result->has_val = 1; + } else if (type == PRE_OP || type == POST_OP || type == BIN_OP) { + result->op = parse; + result->op_type = type; + } + + EC_PARSE_FOREACH_CHILD(child, parse) { + + type = get_node_type(expr_gen_node, ec_parse_get_node(child)); + if (type == PAREN_OPEN) { + open = child; + continue; + } else if (type == PAREN_CLOSE) { + close = child; + continue; + } + + if (eval_expression(&child_result, userctx, ops, + expr_gen_node, child) < 0) + goto fail; + + if (merge_results(userctx, ops, result, &child_result) < 0) + goto fail; + + memset(&child_result, 0, sizeof(child_result)); + } + + if (open != NULL && close != NULL) { + if (ops->eval_parenthesis(&result->val, userctx, open, close, + result->val) < 0) + goto fail; + } + + return 0; + +fail: + if (result->has_val) + ops->eval_free(result->val, userctx); + if (child_result.has_val) + ops->eval_free(child_result.val, userctx); + memset(result, 0, sizeof(*result)); + + return -1; +} + +int ec_node_expr_eval(void **user_result, const struct ec_node *node, + struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops, + void *userctx) +{ + struct result result; + + if (ops == NULL || ops->eval_var == NULL || ops->eval_pre_op == NULL || + ops->eval_post_op == NULL || ops->eval_bin_op == NULL || + ops->eval_parenthesis == NULL || + ops->eval_free == NULL) { + errno = EINVAL; + return -1; + } + + if (ec_node_check_type(node, &ec_node_expr_type) < 0) + return -1; + + if (!ec_parse_matches(parse)) { + errno = EINVAL; + return -1; + } + + if (eval_expression(&result, userctx, ops, node, parse) < 0) + return -1; + + assert(result.has_val); + assert(result.op == NULL); + *user_result = result.val; + + return 0; +} + +/* the test case is in a separate file ecoli_node_expr_test.c */ diff --git a/libecoli/ecoli_node_expr.h b/libecoli/ecoli_node_expr.h new file mode 100644 index 0000000..4f21d81 --- /dev/null +++ b/libecoli/ecoli_node_expr.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_EXPR_ +#define ECOLI_NODE_EXPR_ + +#include + +/** + * Callback function type for evaluating a variable + * + * @param result + * On success, this pointer must be set by the user to point + * to a user structure describing the evaluated result. + * @param userctx + * A user-defined context passed to all callback functions, which + * can be used to maintain a state or store global information. + * @param var + * The parse result referencing the variable. + * @return + * 0 on success (*result must be set), or -errno on error (*result + * is undefined). + */ +typedef int (*ec_node_expr_eval_var_t)( + void **result, void *userctx, + const struct ec_parse *var); + +/** + * Callback function type for evaluating a prefix-operator + * + * @param result + * On success, this pointer must be set by the user to point + * to a user structure describing the evaluated result. + * @param userctx + * A user-defined context passed to all callback functions, which + * can be used to maintain a state or store global information. + * @param operand + * The evaluated expression on which the operation should be applied. + * @param var + * The parse result referencing the operator. + * @return + * 0 on success (*result must be set, operand is freed), + * or -errno on error (*result is undefined, operand is not freed). + */ +typedef int (*ec_node_expr_eval_pre_op_t)( + void **result, void *userctx, + void *operand, + const struct ec_parse *operator); + +typedef int (*ec_node_expr_eval_post_op_t)( + void **result, void *userctx, + void *operand, + const struct ec_parse *operator); + +typedef int (*ec_node_expr_eval_bin_op_t)( + void **result, void *userctx, + void *operand1, + const struct ec_parse *operator, + void *operand2); + +typedef int (*ec_node_expr_eval_parenthesis_t)( + void **result, void *userctx, + const struct ec_parse *open_paren, + const struct ec_parse *close_paren, + void * value); + +typedef void (*ec_node_expr_eval_free_t)( + void *result, void *userctx); + + +struct ec_node *ec_node_expr(const char *id); +int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node); +int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op); +int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op); +int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op); +int ec_node_expr_add_parenthesis(struct ec_node *gen_node, + struct ec_node *open, struct ec_node *close); + +struct ec_node_expr_eval_ops { + ec_node_expr_eval_var_t eval_var; + ec_node_expr_eval_pre_op_t eval_pre_op; + ec_node_expr_eval_post_op_t eval_post_op; + ec_node_expr_eval_bin_op_t eval_bin_op; + ec_node_expr_eval_parenthesis_t eval_parenthesis; + ec_node_expr_eval_free_t eval_free; +}; + +int ec_node_expr_eval(void **result, const struct ec_node *node, + struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops, + void *userctx); + +#endif diff --git a/libecoli/ecoli_node_expr_test.c b/libecoli/ecoli_node_expr_test.c new file mode 100644 index 0000000..93e33a4 --- /dev/null +++ b/libecoli/ecoli_node_expr_test.c @@ -0,0 +1,308 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_expr); + +struct my_eval_result { + int val; +}; + +static int +ec_node_expr_test_eval_var(void **result, void *userctx, + const struct ec_parse *var) +{ + const struct ec_strvec *vec; + const struct ec_node *node; + struct my_eval_result *eval = NULL; + int64_t val; + + (void)userctx; + + /* get parsed string vector, it should contain only one str */ + vec = ec_parse_strvec(var); + if (ec_strvec_len(vec) != 1) { + errno = EINVAL; + return -1; + } + + node = ec_parse_get_node(var); + if (ec_node_int_getval(node, ec_strvec_val(vec, 0), &val) < 0) + return -1; + + eval = ec_malloc(sizeof(*eval)); + if (eval == NULL) + return -1; + + eval->val = val; + EC_LOG(EC_LOG_DEBUG, "eval var %d\n", eval->val); + *result = eval; + + return 0; +} + +static int +ec_node_expr_test_eval_pre_op(void **result, void *userctx, void *operand, + const struct ec_parse *operator) +{ + const struct ec_strvec *vec; + struct my_eval_result *eval = operand;; + + (void)userctx; + + /* get parsed string vector, it should contain only one str */ + vec = ec_parse_strvec(operator); + if (ec_strvec_len(vec) != 1) { + errno = EINVAL; + return -1; + } + + if (!strcmp(ec_strvec_val(vec, 0), "!")) { + eval->val = !eval->val; + } else { + errno = EINVAL; + return -1; + } + + + EC_LOG(EC_LOG_DEBUG, "eval pre_op %d\n", eval->val); + *result = eval; + + return 0; +} + +static int +ec_node_expr_test_eval_post_op(void **result, void *userctx, void *operand, + const struct ec_parse *operator) +{ + const struct ec_strvec *vec; + struct my_eval_result *eval = operand;; + + (void)userctx; + + /* get parsed string vector, it should contain only one str */ + vec = ec_parse_strvec(operator); + if (ec_strvec_len(vec) != 1) { + errno = EINVAL; + return -1; + } + + if (!strcmp(ec_strvec_val(vec, 0), "^")) { + eval->val = eval->val * eval->val; + } else { + errno = EINVAL; + return -1; + } + + EC_LOG(EC_LOG_DEBUG, "eval post_op %d\n", eval->val); + *result = eval; + + return 0; +} + +static int +ec_node_expr_test_eval_bin_op(void **result, void *userctx, void *operand1, + const struct ec_parse *operator, void *operand2) + +{ + const struct ec_strvec *vec; + struct my_eval_result *eval1 = operand1;; + struct my_eval_result *eval2 = operand2;; + + (void)userctx; + + /* get parsed string vector, it should contain only one str */ + vec = ec_parse_strvec(operator); + if (ec_strvec_len(vec) != 1) { + errno = EINVAL; + return -1; + } + + if (!strcmp(ec_strvec_val(vec, 0), "+")) { + eval1->val = eval1->val + eval2->val; + } else if (!strcmp(ec_strvec_val(vec, 0), "*")) { + eval1->val = eval1->val * eval2->val; + } else { + errno = EINVAL; + return -1; + } + + EC_LOG(EC_LOG_DEBUG, "eval bin_op %d\n", eval1->val); + ec_free(eval2); + *result = eval1; + + return 0; +} + +static int +ec_node_expr_test_eval_parenthesis(void **result, void *userctx, + const struct ec_parse *open_paren, + const struct ec_parse *close_paren, + void *value) +{ + (void)userctx; + (void)open_paren; + (void)close_paren; + + EC_LOG(EC_LOG_DEBUG, "eval paren\n"); + *result = value; + + return 0; +} + +static void +ec_node_expr_test_eval_free(void *result, void *userctx) +{ + (void)userctx; + ec_free(result); +} + +static const struct ec_node_expr_eval_ops test_ops = { + .eval_var = ec_node_expr_test_eval_var, + .eval_pre_op = ec_node_expr_test_eval_pre_op, + .eval_post_op = ec_node_expr_test_eval_post_op, + .eval_bin_op = ec_node_expr_test_eval_bin_op, + .eval_parenthesis = ec_node_expr_test_eval_parenthesis, + .eval_free = ec_node_expr_test_eval_free, +}; + +static int ec_node_expr_test_eval(struct ec_node *lex_node, + const struct ec_node *expr_node, + const char *str, int val) +{ + struct ec_parse *p; + void *result; + struct my_eval_result *eval; + int ret; + + p = ec_node_parse(lex_node, str); + if (p == NULL) + return -1; + + ret = ec_node_expr_eval(&result, expr_node, p, &test_ops, NULL); + ec_parse_free(p); + if (ret < 0) + return -1; + + /* the parse value is an integer */ + eval = result; + assert(eval != NULL); + + EC_LOG(EC_LOG_DEBUG, "result: %d (expected %d)\n", eval->val, val); + if (eval->val == val) + ret = 0; + else + ret = -1; + + ec_free(eval); + + return ret; +} + +/* LCOV_EXCL_START */ +static int ec_node_expr_testcase(void) +{ + struct ec_node *node = NULL, *lex_node = NULL; + int testres = 0; + + node = ec_node("expr", "my_expr"); + if (node == NULL) + return -1; + + ec_node_expr_set_val_node(node, ec_node_int(EC_NO_ID, 0, UCHAR_MAX, 0)); + ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "+")); + ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "*")); + ec_node_expr_add_pre_op(node, ec_node_str(EC_NO_ID, "!")); /* not */ + ec_node_expr_add_post_op(node, ec_node_str(EC_NO_ID, "^")); /* square */ + ec_node_expr_add_parenthesis(node, ec_node_str(EC_NO_ID, "("), + ec_node_str(EC_NO_ID, ")")); + testres |= EC_TEST_CHECK_PARSE(node, 1, "1"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "*"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1", "*"); + testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "+", "!", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "^", "+", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "*", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "+", "1"); + testres |= EC_TEST_CHECK_PARSE(node, 7, "1", "*", "1", "*", "1", "*", + "1"); + testres |= EC_TEST_CHECK_PARSE( + node, 10, "!", "(", "1", "*", "(", "1", "+", "1", ")", ")"); + testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "+", "!", "1", "^"); + + /* prepend a lexer to the expression node */ + lex_node = ec_node_re_lex(EC_NO_ID, ec_node_clone(node)); + if (lex_node == NULL) + goto fail; + + testres |= ec_node_re_lex_add(lex_node, "[0-9]+", 1); /* vars */ + testres |= ec_node_re_lex_add(lex_node, "[+*!^()]", 1); /* operators */ + testres |= ec_node_re_lex_add(lex_node, "[ ]+", 0); /* spaces */ + + /* valid expressions */ + testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!1"); + testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^"); + testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^ + 1"); + testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1 + 4 * (2 + 3^)^"); + testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1)"); + testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "3*!3+!3*(2+ 2)"); + testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!!(!1)^ + !(4 + (2*3))"); + testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1 + 1)^ * 1^"); + + /* invalid expressions */ + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, ""); + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "()"); + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "("); + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, ")"); + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "+1"); + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+"); + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+*1"); + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+(1*1"); + testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+!1!1)"); + + testres |= ec_node_expr_test_eval(lex_node, node, "1^", 1); + testres |= ec_node_expr_test_eval(lex_node, node, "2^", 4); + testres |= ec_node_expr_test_eval(lex_node, node, "!1", 0); + testres |= ec_node_expr_test_eval(lex_node, node, "!0", 1); + + testres |= ec_node_expr_test_eval(lex_node, node, "1+1", 2); + testres |= ec_node_expr_test_eval(lex_node, node, "1+2+3", 6); + testres |= ec_node_expr_test_eval(lex_node, node, "1+1*2", 4); + testres |= ec_node_expr_test_eval(lex_node, node, "2 * 2^", 8); + testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !0)^ * !0^", 4); + testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !1) * 3", 3); + + ec_node_free(node); + ec_node_free(lex_node); + + return testres; + +fail: + ec_node_free(lex_node); + ec_node_free(node); + return -1; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_expr_test = { + .name = "node_expr", + .test = ec_node_expr_testcase, +}; + +EC_TEST_REGISTER(ec_node_expr_test); diff --git a/libecoli/ecoli_node_file.c b/libecoli/ecoli_node_file.c new file mode 100644 index 0000000..001dcb6 --- /dev/null +++ b/libecoli/ecoli_node_file.c @@ -0,0 +1,425 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_file); + +struct ec_node_file { + struct ec_node gen; + + /* below functions pointers are only useful for test */ + int (*lstat)(const char *pathname, struct stat *buf); + DIR *(*opendir)(const char *name); + struct dirent *(*readdir)(DIR *dirp); + int (*closedir)(DIR *dirp); + int (*dirfd)(DIR *dirp); + int (*fstatat)(int dirfd, const char *pathname, struct stat *buf, + int flags); +}; + +static int +ec_node_file_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + (void)gen_node; + (void)state; + + if (ec_strvec_len(strvec) == 0) + return EC_PARSE_NOMATCH; + + return 1; +} + +/* + * Almost the same than dirname (3) and basename (3) except that: + * - it always returns a substring of the given path, which can + * be empty. + * - the behavior is different when the path finishes with a '/' + * - the path argument is not modified + * - the outputs are allocated and must be freed with ec_free(). + * + * path dirname basename split_path + * /usr/lib /usr lib /usr/ lib + * /usr/ / usr /usr/ + * usr . usr usr + * / / / / + * . . . . + * .. . .. .. + */ +static int split_path(const char *path, char **dname_p, char **bname_p) +{ + char *last_slash; + size_t dirlen; + char *dname, *bname; + + *dname_p = NULL; + *bname_p = NULL; + + last_slash = strrchr(path, '/'); + if (last_slash == NULL) + dirlen = 0; + else + dirlen = last_slash - path + 1; + + dname = ec_strdup(path); + if (dname == NULL) + return -1; + dname[dirlen] = '\0'; + + bname = ec_strdup(path + dirlen); + if (bname == NULL) { + ec_free(dname); + return -1; + } + + *dname_p = dname; + *bname_p = bname; + + return 0; +} + +static int +ec_node_file_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_file *node = (struct ec_node_file *)gen_node; + char *dname = NULL, *bname = NULL, *effective_dir; + struct ec_comp_item *item = NULL; + enum ec_comp_type type; + struct stat st, st2; + const char *input; + size_t bname_len; + struct dirent *de = NULL; + DIR *dir = NULL; + char *comp_str = NULL; + char *disp_str = NULL; + int is_dir = 0; + + /* + * Example with this file tree: + * / + * ├── dir1 + * │   ├── file1 + * │   ├── file2 + * │   └── subdir + * │   └── file3 + * ├── dir2 + * │   └── file4 + * └── file5 + * + * Input Output completions + * / [dir1/, dir2/, file5] + * /d [dir1/, dir2/] + * /f [file5] + * /dir1/ [file1, file2, subdir/] + * + * + * + */ + + if (ec_strvec_len(strvec) != 1) + return 0; + + input = ec_strvec_val(strvec, 0); + if (split_path(input, &dname, &bname) < 0) + return -1; + + if (strcmp(dname, "") == 0) + effective_dir = "."; + else + effective_dir = dname; + + if (node->lstat(effective_dir, &st) < 0) + goto fail; + if (!S_ISDIR(st.st_mode)) + goto out; + + dir = node->opendir(effective_dir); + if (dir == NULL) + goto fail; + + bname_len = strlen(bname); + while (1) { + int save_errno = errno; + + errno = 0; + de = node->readdir(dir); + if (de == NULL) { + if (errno == 0) { + errno = save_errno; + goto out; + } else { + goto fail; + } + } + + if (!ec_str_startswith(de->d_name, bname)) + continue; + if (bname[0] != '.' && de->d_name[0] == '.') + continue; + + /* add '/' if it's a dir */ + if (de->d_type == DT_DIR) { + is_dir = 1; + } else if (de->d_type == DT_UNKNOWN) { + int dir_fd = node->dirfd(dir); + + if (dir_fd < 0) + goto fail; + if (node->fstatat(dir_fd, de->d_name, &st2, 0) < 0) + goto fail; + if (S_ISDIR(st2.st_mode)) + is_dir = 1; + else + is_dir = 0; + } else { + is_dir = 0; + } + + if (is_dir) { + type = EC_COMP_PARTIAL; + if (ec_asprintf(&comp_str, "%s%s/", input, + &de->d_name[bname_len]) < 0) + goto fail; + if (ec_asprintf(&disp_str, "%s/", de->d_name) < 0) + goto fail; + } else { + type = EC_COMP_FULL; + if (ec_asprintf(&comp_str, "%s%s", input, + &de->d_name[bname_len]) < 0) + goto fail; + if (ec_asprintf(&disp_str, "%s", de->d_name) < 0) + goto fail; + } + if (ec_comp_add_item(comp, gen_node, &item, + type, input, comp_str) < 0) + goto out; + + /* fix the display string: we don't want to display the full + * path. */ + if (ec_comp_item_set_display(item, disp_str) < 0) + goto out; + + item = NULL; + ec_free(comp_str); + comp_str = NULL; + ec_free(disp_str); + disp_str = NULL; + } +out: + ec_free(comp_str); + ec_free(disp_str); + ec_free(dname); + ec_free(bname); + if (dir != NULL) + node->closedir(dir); + + return 0; + +fail: + ec_free(comp_str); + ec_free(disp_str); + ec_free(dname); + ec_free(bname); + if (dir != NULL) + node->closedir(dir); + + return -1; +} + +static int +ec_node_file_init_priv(struct ec_node *gen_node) +{ + struct ec_node_file *node = (struct ec_node_file *)gen_node; + + node->lstat = lstat; + node->opendir = opendir; + node->readdir = readdir; + node->dirfd = dirfd; + node->fstatat = fstatat; + + return 0; +} + +static struct ec_node_type ec_node_file_type = { + .name = "file", + .parse = ec_node_file_parse, + .complete = ec_node_file_complete, + .size = sizeof(struct ec_node_file), + .init_priv = ec_node_file_init_priv, +}; + +EC_NODE_TYPE_REGISTER(ec_node_file_type); + +/* LCOV_EXCL_START */ +static int +test_lstat(const char *pathname, struct stat *buf) +{ + if (!strcmp(pathname, "/tmp/toto/")) { + struct stat st = { .st_mode = S_IFDIR }; + memcpy(buf, &st, sizeof(*buf)); + return 0; + } + + errno = ENOENT; + return -1; +} + +static DIR * +test_opendir(const char *name) +{ + int *p; + + if (strcmp(name, "/tmp/toto/")) { + errno = ENOENT; + return NULL; + } + + p = malloc(sizeof(int)); + if (p) + *p = 0; + + return (DIR *)p; +} + +static struct dirent * +test_readdir(DIR *dirp) +{ + static struct dirent de[] = { + { .d_type = DT_DIR, .d_name = ".." }, + { .d_type = DT_DIR, .d_name = "." }, + { .d_type = DT_REG, .d_name = "bar" }, + { .d_type = DT_UNKNOWN, .d_name = "bar2" }, + { .d_type = DT_REG, .d_name = "foo" }, + { .d_type = DT_DIR, .d_name = "titi" }, + { .d_type = DT_UNKNOWN, .d_name = "tutu" }, + { .d_name = "" }, + }; + int *p = (int *)dirp; + struct dirent *ret = &de[*p]; + + if (!strcmp(ret->d_name, "")) + return NULL; + + *p = *p + 1; + + return ret; +} + +static int +test_closedir(DIR *dirp) +{ + free(dirp); + return 0; +} + +static int +test_dirfd(DIR *dirp) +{ + int *p = (int *)dirp; + return *p; +} + +static int +test_fstatat(int dirfd, const char *pathname, struct stat *buf, + int flags) +{ + (void)dirfd; + (void)flags; + + if (!strcmp(pathname, "bar2")) { + struct stat st = { .st_mode = S_IFREG }; + memcpy(buf, &st, sizeof(*buf)); + return 0; + } else if (!strcmp(pathname, "tutu")) { + struct stat st = { .st_mode = S_IFDIR }; + memcpy(buf, &st, sizeof(*buf)); + return 0; + } + + errno = ENOENT; + return -1; +} + +static int +ec_node_file_override_functions(struct ec_node *gen_node) +{ + struct ec_node_file *node = (struct ec_node_file *)gen_node; + + node->lstat = test_lstat; + node->opendir = test_opendir; + node->readdir = test_readdir; + node->closedir = test_closedir; + node->dirfd = test_dirfd; + node->fstatat = test_fstatat; + + return 0; +} + +static int ec_node_file_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node("file", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + ec_node_file_override_functions(node); + + /* any string matches */ + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1); + + /* test completion */ + testres |= EC_TEST_CHECK_COMPLETE(node, + EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "/tmp/toto/t", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE_PARTIAL(node, + "/tmp/toto/t", EC_NODE_ENDLIST, + "/tmp/toto/titi/", "/tmp/toto/tutu/", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "/tmp/toto/f", EC_NODE_ENDLIST, + "/tmp/toto/foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "/tmp/toto/b", EC_NODE_ENDLIST, + "/tmp/toto/bar", "/tmp/toto/bar2", EC_NODE_ENDLIST); + + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_file_test = { + .name = "node_file", + .test = ec_node_file_testcase, +}; + +EC_TEST_REGISTER(ec_node_file_test); diff --git a/libecoli/ecoli_node_file.h b/libecoli/ecoli_node_file.h new file mode 100644 index 0000000..5760902 --- /dev/null +++ b/libecoli/ecoli_node_file.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_FILE_ +#define ECOLI_NODE_FILE_ + +#include + +struct ec_node *ec_node_file(const char *id, const char *file); + +/* file is duplicated */ +int ec_node_file_set_str(struct ec_node *node, const char *file); + +#endif diff --git a/libecoli/ecoli_node_helper.c b/libecoli/ecoli_node_helper.c new file mode 100644 index 0000000..9ec7e89 --- /dev/null +++ b/libecoli/ecoli_node_helper.c @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct ec_node ** +ec_node_config_node_list_to_table(const struct ec_config *config, + size_t *len) +{ + struct ec_node **table = NULL; + struct ec_config *child; + size_t n, i; + + *len = 0; + + if (config == NULL) { + errno = EINVAL; + return NULL; + } + + if (ec_config_get_type(config) != EC_CONFIG_TYPE_LIST) { + errno = EINVAL; + return NULL; + } + + n = 0; + TAILQ_FOREACH(child, &config->list, next) { + if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { + errno = EINVAL; + return NULL; + } + n++; + } + + table = ec_malloc(n * sizeof(*table)); + if (table == NULL) + goto fail; + + n = 0; + TAILQ_FOREACH(child, &config->list, next) { + table[n] = ec_node_clone(child->node); + n++; + } + + *len = n; + + return table; + +fail: + if (table != NULL) { + for (i = 0; i < n; i++) + ec_node_free(table[i]); + } + ec_free(table); + + return NULL; +} + +struct ec_config * +ec_node_config_node_list_from_vargs(va_list ap) +{ + struct ec_config *list = NULL; + struct ec_node *node = va_arg(ap, struct ec_node *); + + list = ec_config_list(); + if (list == NULL) + goto fail; + + for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *)) { + if (node == NULL) + goto fail; + + if (ec_config_list_add(list, ec_config_node(node)) < 0) + goto fail; + } + + return list; + +fail: + for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *)) + ec_node_free(node); + ec_config_free(list); + + return NULL; +} diff --git a/libecoli/ecoli_node_helper.h b/libecoli/ecoli_node_helper.h new file mode 100644 index 0000000..9dbf519 --- /dev/null +++ b/libecoli/ecoli_node_helper.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +/** + * Helpers that are commonly used in nodes. + */ + +#ifndef ECOLI_NODE_HELPERS_ +#define ECOLI_NODE_HELPERS + +struct ec_node; + +/** + * Build a node table from a node list in a ec_config. + * + * The function takes a node configuration as parameter, which must be a + * node list. From it, a node table is built. A reference is taken for + * each node. + * + * On error, no reference is taken. + * + * @param config + * The configuration (type must be a list of nodes). If it is + * NULL, an error is returned. + * @param len + * The length of the allocated table on success, or 0 on error. + * @return + * The allocated node table, that must be freed by the caller: + * each entry must be freed with ec_node_free() and the table + * with ec_free(). On error, NULL is returned and errno is set. + */ +struct ec_node ** +ec_node_config_node_list_to_table(const struct ec_config *config, + size_t *len); + +/** + * Build a list of config nodes from variable arguments. + * + * The va_list argument is a list of pointer to ec_node structures, + * terminated with EC_NODE_ENDLIST. + * + * This helper is used by nodes that contain a list of nodes, + * like "seq", "or", ... + * + * @param ap + * List of pointer to ec_node structures, terminated with + * EC_NODE_ENDLIST. + * @return + * A pointer to an ec_config structure. In this case, the + * nodes will be freed when the config structure will be freed. + * On error, NULL is returned (and errno is set), and the + * nodes are freed. + */ +struct ec_config * +ec_node_config_node_list_from_vargs(va_list ap); + +#endif diff --git a/libecoli/ecoli_node_int.c b/libecoli/ecoli_node_int.c new file mode 100644 index 0000000..9b56e22 --- /dev/null +++ b/libecoli/ecoli_node_int.c @@ -0,0 +1,501 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_int); + +/* common to int and uint */ +struct ec_node_int_uint { + struct ec_node gen; + bool is_signed; + bool check_min; + bool check_max; + union { + int64_t min; + uint64_t umin; + }; + union { + int64_t max; + uint64_t umax; + }; + unsigned int base; +}; + +/* XXX to utils.c ? */ +static int parse_llint(struct ec_node_int_uint *node, const char *str, + int64_t *val) +{ + char *endptr; + int save_errno = errno; + + errno = 0; + *val = strtoll(str, &endptr, node->base); + + if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) || + (errno != 0 && *val == 0)) + return -1; + + if (node->check_min && *val < node->min) { + errno = ERANGE; + return -1; + } + + if (node->check_max && *val > node->max) { + errno = ERANGE; + return -1; + } + + if (*endptr != 0) { + errno = EINVAL; + return -1; + } + + errno = save_errno; + return 0; +} + +static int parse_ullint(struct ec_node_int_uint *node, const char *str, + uint64_t *val) +{ + char *endptr; + int save_errno = errno; + + /* since a negative input is silently converted to a positive + * one by strtoull(), first check that it is positive */ + if (strchr(str, '-')) + return -1; + + errno = 0; + *val = strtoull(str, &endptr, node->base); + + if ((errno == ERANGE && *val == ULLONG_MAX) || + (errno != 0 && *val == 0)) + return -1; + + if (node->check_min && *val < node->umin) + return -1; + + if (node->check_max && *val > node->umax) + return -1; + + if (*endptr != 0) + return -1; + + errno = save_errno; + return 0; +} + +static int ec_node_int_uint_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; + const char *str; + uint64_t u64; + int64_t i64; + + (void)state; + + if (ec_strvec_len(strvec) == 0) + return EC_PARSE_NOMATCH; + + str = ec_strvec_val(strvec, 0); + if (node->is_signed) { + if (parse_llint(node, str, &i64) < 0) + return EC_PARSE_NOMATCH; + } else { + if (parse_ullint(node, str, &u64) < 0) + return EC_PARSE_NOMATCH; + } + return 1; +} + +static int +ec_node_uint_init_priv(struct ec_node *gen_node) +{ + struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; + + node->is_signed = true; + + return 0; +} + +static const struct ec_config_schema ec_node_int_schema[] = { + { + .key = "min", + .desc = "The minimum valid value (included).", + .type = EC_CONFIG_TYPE_INT64, + }, + { + .key = "max", + .desc = "The maximum valid value (included).", + .type = EC_CONFIG_TYPE_INT64, + }, + { + .key = "base", + .desc = "The base to use. If unset or 0, try to guess.", + .type = EC_CONFIG_TYPE_UINT64, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_int_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; + const struct ec_config *min_value = NULL; + const struct ec_config *max_value = NULL; + const struct ec_config *base_value = NULL; + char *s = NULL; + + min_value = ec_config_dict_get(config, "min"); + max_value = ec_config_dict_get(config, "max"); + base_value = ec_config_dict_get(config, "base"); + + if (min_value && max_value && min_value->i64 > max_value->i64) { + errno = EINVAL; + goto fail; + } + + if (min_value != NULL) { + node->check_min = true; + node->min = min_value->i64; + } else { + node->check_min = false; + } + if (max_value != NULL) { + node->check_max = true; + node->max = max_value->i64; + } else { + node->check_min = false; + } + if (base_value != NULL) + node->base = base_value->u64; + else + node->base = 0; + + return 0; + +fail: + ec_free(s); + return -1; +} + +static struct ec_node_type ec_node_int_type = { + .name = "int", + .schema = ec_node_int_schema, + .set_config = ec_node_int_set_config, + .parse = ec_node_int_uint_parse, + .complete = ec_node_complete_unknown, + .size = sizeof(struct ec_node_int_uint), + .init_priv = ec_node_uint_init_priv, +}; + +EC_NODE_TYPE_REGISTER(ec_node_int_type); + +struct ec_node *ec_node_int(const char *id, int64_t min, + int64_t max, unsigned int base) +{ + struct ec_config *config = NULL; + struct ec_node *gen_node = NULL; + int ret; + + gen_node = ec_node_from_type(&ec_node_int_type, id); + if (gen_node == NULL) + return NULL; + + config = ec_config_dict(); + if (config == NULL) + goto fail; + + ret = ec_config_dict_set(config, "min", ec_config_i64(min)); + if (ret < 0) + goto fail; + ret = ec_config_dict_set(config, "max", ec_config_i64(max)); + if (ret < 0) + goto fail; + ret = ec_config_dict_set(config, "base", ec_config_u64(base)); + if (ret < 0) + goto fail; + + ret = ec_node_set_config(gen_node, config); + config = NULL; + if (ret < 0) + goto fail; + + return gen_node; + +fail: + ec_config_free(config); + ec_node_free(gen_node); + return NULL; +} + +static const struct ec_config_schema ec_node_uint_schema[] = { + { + .key = "min", + .desc = "The minimum valid value (included).", + .type = EC_CONFIG_TYPE_UINT64, + }, + { + .key = "max", + .desc = "The maximum valid value (included).", + .type = EC_CONFIG_TYPE_UINT64, + }, + { + .key = "base", + .desc = "The base to use. If unset or 0, try to guess.", + .type = EC_CONFIG_TYPE_UINT64, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_uint_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; + const struct ec_config *min_value = NULL; + const struct ec_config *max_value = NULL; + const struct ec_config *base_value = NULL; + char *s = NULL; + + min_value = ec_config_dict_get(config, "min"); + max_value = ec_config_dict_get(config, "max"); + base_value = ec_config_dict_get(config, "base"); + + if (min_value && max_value && min_value->u64 > max_value->u64) { + errno = EINVAL; + goto fail; + } + + if (min_value != NULL) { + node->check_min = true; + node->min = min_value->u64; + } else { + node->check_min = false; + } + if (max_value != NULL) { + node->check_max = true; + node->max = max_value->u64; + } else { + node->check_min = false; + } + if (base_value != NULL) + node->base = base_value->u64; + else + node->base = 0; + + return 0; + +fail: + ec_free(s); + return -1; +} + +static struct ec_node_type ec_node_uint_type = { + .name = "uint", + .schema = ec_node_uint_schema, + .set_config = ec_node_uint_set_config, + .parse = ec_node_int_uint_parse, + .complete = ec_node_complete_unknown, + .size = sizeof(struct ec_node_int_uint), +}; + +EC_NODE_TYPE_REGISTER(ec_node_uint_type); + +struct ec_node *ec_node_uint(const char *id, uint64_t min, + uint64_t max, unsigned int base) +{ + struct ec_config *config = NULL; + struct ec_node *gen_node = NULL; + int ret; + + gen_node = ec_node_from_type(&ec_node_uint_type, id); + if (gen_node == NULL) + return NULL; + + config = ec_config_dict(); + if (config == NULL) + goto fail; + + ret = ec_config_dict_set(config, "min", ec_config_u64(min)); + if (ret < 0) + goto fail; + ret = ec_config_dict_set(config, "max", ec_config_u64(max)); + if (ret < 0) + goto fail; + ret = ec_config_dict_set(config, "base", ec_config_u64(base)); + if (ret < 0) + goto fail; + + ret = ec_node_set_config(gen_node, config); + config = NULL; + if (ret < 0) + goto fail; + + return gen_node; + +fail: + ec_config_free(config); + ec_node_free(gen_node); + return NULL; +} + +int ec_node_int_getval(const struct ec_node *gen_node, const char *str, + int64_t *result) +{ + struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; + int ret; + + ret = ec_node_check_type(gen_node, &ec_node_int_type); + if (ret < 0) + return ret; + + if (parse_llint(node, str, result) < 0) + return -1; + + return 0; +} + +int ec_node_uint_getval(const struct ec_node *gen_node, const char *str, + uint64_t *result) +{ + struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node; + int ret; + + ret = ec_node_check_type(gen_node, &ec_node_uint_type); + if (ret < 0) + return ret; + + if (parse_ullint(node, str, result) < 0) + return -1; + + return 0; +} + +/* LCOV_EXCL_START */ +static int ec_node_int_testcase(void) +{ + struct ec_parse *p; + struct ec_node *node; + const char *s; + int testres = 0; + uint64_t u64; + int64_t i64; + + node = ec_node_uint(EC_NO_ID, 1, 256, 0); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, -1, ""); + testres |= EC_TEST_CHECK_PARSE(node, -1, "0"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "1"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "256", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "0x100"); + testres |= EC_TEST_CHECK_PARSE(node, 1, " 1"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "-1"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "0x101"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "0x100000000000000000"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "4r"); + + p = ec_node_parse(node, "1"); + s = ec_strvec_val(ec_parse_strvec(p), 0); + testres |= EC_TEST_CHECK(s != NULL && + ec_node_uint_getval(node, s, &u64) == 0 && + u64 == 1, "bad integer value"); + ec_parse_free(p); + + p = ec_node_parse(node, "10"); + s = ec_strvec_val(ec_parse_strvec(p), 0); + testres |= EC_TEST_CHECK(s != NULL && + ec_node_uint_getval(node, s, &u64) == 0 && + u64 == 10, "bad integer value"); + ec_parse_free(p); + ec_node_free(node); + + node = ec_node_int(EC_NO_ID, -1, LLONG_MAX, 16); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "0"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "-1"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "7fffffffffffffff"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "0x7fffffffffffffff"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "0x8000000000000000"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "-2"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "4r"); + + p = ec_node_parse(node, "10"); + s = ec_strvec_val(ec_parse_strvec(p), 0); + testres |= EC_TEST_CHECK(s != NULL && + ec_node_int_getval(node, s, &i64) == 0 && + i64 == 16, "bad integer value"); + ec_parse_free(p); + ec_node_free(node); + + node = ec_node_int(EC_NO_ID, LLONG_MIN, 0, 10); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "0"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "-1"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "-9223372036854775808"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "0x0"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "1"); + ec_node_free(node); + + /* test completion */ + node = ec_node_int(EC_NO_ID, 0, 10, 0); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "x", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "1", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_int_test = { + .name = "node_int", + .test = ec_node_int_testcase, +}; + +EC_TEST_REGISTER(ec_node_int_test); diff --git a/libecoli/ecoli_node_int.h b/libecoli/ecoli_node_int.h new file mode 100644 index 0000000..b64c60c --- /dev/null +++ b/libecoli/ecoli_node_int.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_INT_ +#define ECOLI_NODE_INT_ + +#include + +#include + +/* ec_node("int", ...) can be used too + * default is no limit, base 10 */ + +struct ec_node *ec_node_int(const char *id, int64_t min, + int64_t max, unsigned int base); + +int ec_node_int_getval(const struct ec_node *node, const char *str, + int64_t *result); + + + +struct ec_node *ec_node_uint(const char *id, uint64_t min, + uint64_t max, unsigned int base); + +int ec_node_uint_getval(const struct ec_node *node, const char *str, + uint64_t *result); + + +#endif diff --git a/libecoli/ecoli_node_many.c b/libecoli/ecoli_node_many.c new file mode 100644 index 0000000..1c91f85 --- /dev/null +++ b/libecoli/ecoli_node_many.c @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_many); + +struct ec_node_many { + struct ec_node gen; + unsigned int min; + unsigned int max; + struct ec_node *child; +}; + +static int ec_node_many_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_many *node = (struct ec_node_many *)gen_node; + struct ec_parse *child_parse; + struct ec_strvec *childvec = NULL; + size_t off = 0, count; + int ret; + + for (count = 0; node->max == 0 || count < node->max; count++) { + childvec = ec_strvec_ndup(strvec, off, + ec_strvec_len(strvec) - off); + if (childvec == NULL) + goto fail; + + ret = ec_node_parse_child(node->child, state, childvec); + if (ret < 0) + goto fail; + + ec_strvec_free(childvec); + childvec = NULL; + + if (ret == EC_PARSE_NOMATCH) + break; + + /* it matches an empty strvec, no need to continue */ + if (ret == 0) { + child_parse = ec_parse_get_last_child(state); + ec_parse_unlink_child(state, child_parse); + ec_parse_free(child_parse); + break; + } + + off += ret; + } + + if (count < node->min) { + ec_parse_free_children(state); + return EC_PARSE_NOMATCH; + } + + return off; + +fail: + ec_strvec_free(childvec); + return -1; +} + +static int +__ec_node_many_complete(struct ec_node_many *node, unsigned int max, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_parse *parse = ec_comp_get_state(comp); + struct ec_strvec *childvec = NULL; + unsigned int i; + int ret; + + /* first, try to complete with the child node */ + ret = ec_node_complete_child(node->child, comp, strvec); + if (ret < 0) + goto fail; + + /* we're done, we reached the max number of nodes */ + if (max == 1) + return 0; + + /* if there is a maximum, decrease it before recursion */ + if (max != 0) + max--; + + /* then, if the node matches the beginning of the strvec, try to + * complete the rest */ + for (i = 0; i < ec_strvec_len(strvec); i++) { + childvec = ec_strvec_ndup(strvec, 0, i); + if (childvec == NULL) + goto fail; + + ret = ec_node_parse_child(node->child, parse, childvec); + if (ret < 0) + goto fail; + + ec_strvec_free(childvec); + childvec = NULL; + + if ((unsigned int)ret != i) { + if (ret != EC_PARSE_NOMATCH) + ec_parse_del_last_child(parse); + continue; + } + + childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i); + if (childvec == NULL) { + ec_parse_del_last_child(parse); + goto fail; + } + + ret = __ec_node_many_complete(node, max, comp, childvec); + ec_parse_del_last_child(parse); + ec_strvec_free(childvec); + childvec = NULL; + + if (ret < 0) + goto fail; + } + + return 0; + +fail: + ec_strvec_free(childvec); + return -1; +} + +static int +ec_node_many_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_many *node = (struct ec_node_many *)gen_node; + + return __ec_node_many_complete(node, node->max, comp, + strvec); +} + +static void ec_node_many_free_priv(struct ec_node *gen_node) +{ + struct ec_node_many *node = (struct ec_node_many *)gen_node; + + ec_node_free(node->child); +} + +static size_t +ec_node_many_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_many *node = (struct ec_node_many *)gen_node; + + if (node->child) + return 1; + return 0; +} + +static int +ec_node_many_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_many *node = (struct ec_node_many *)gen_node; + + if (i >= 1) + return -1; + + *child = node->child; + *refs = 1; + return 0; +} + +static struct ec_node_type ec_node_many_type = { + .name = "many", + .parse = ec_node_many_parse, + .complete = ec_node_many_complete, + .size = sizeof(struct ec_node_many), + .free_priv = ec_node_many_free_priv, + .get_children_count = ec_node_many_get_children_count, + .get_child = ec_node_many_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_many_type); + +struct ec_node *ec_node_many(const char *id, struct ec_node *child, + unsigned int min, unsigned int max) +{ + struct ec_node_many *node = NULL; + + if (child == NULL) + return NULL; + + node = (struct ec_node_many *)ec_node_from_type(&ec_node_many_type, id); + if (node == NULL) { + ec_node_free(child); + return NULL; + } + + node->child = child; + node->min = min; + node->max = max; + + return &node->gen; +} + +/* LCOV_EXCL_START */ +static int ec_node_many_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 0, 0); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 0); + testres |= EC_TEST_CHECK_PARSE(node, 0, "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 0); + ec_node_free(node); + + node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 0); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, -1, "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1); + ec_node_free(node); + + node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 2); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, -1, "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1); + ec_node_free(node); + + /* test completion */ + node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 2, 4); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "f", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "foo", "", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "foo", "foo", "", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "foo", "foo", "foo", "", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_many_test = { + .name = "node_many", + .test = ec_node_many_testcase, +}; + +EC_TEST_REGISTER(ec_node_many_test); diff --git a/libecoli/ecoli_node_many.h b/libecoli/ecoli_node_many.h new file mode 100644 index 0000000..14250d1 --- /dev/null +++ b/libecoli/ecoli_node_many.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_MANY_ +#define ECOLI_NODE_MANY_ + +/* + * if min == max == 0, there is no limit + */ +struct ec_node *ec_node_many(const char *id, struct ec_node *child, + unsigned int min, unsigned int max); + +#endif diff --git a/libecoli/ecoli_node_none.c b/libecoli/ecoli_node_none.c new file mode 100644 index 0000000..dba9ebf --- /dev/null +++ b/libecoli/ecoli_node_none.c @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_none); + +struct ec_node_none { + struct ec_node gen; +}; + +static int ec_node_none_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + (void)gen_node; + (void)state; + (void)strvec; + + return EC_PARSE_NOMATCH; +} + +static int +ec_node_none_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + (void)gen_node; + (void)comp; + (void)strvec; + + return 0; +} + +static struct ec_node_type ec_node_none_type = { + .name = "none", + .parse = ec_node_none_parse, + .complete = ec_node_none_complete, + .size = sizeof(struct ec_node_none), +}; + +EC_NODE_TYPE_REGISTER(ec_node_none_type); + +/* LCOV_EXCL_START */ +static int ec_node_none_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node("none", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1); + ec_node_free(node); + + /* never completes */ + node = ec_node("none", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_none_test = { + .name = "node_none", + .test = ec_node_none_testcase, +}; + +EC_TEST_REGISTER(ec_node_none_test); diff --git a/libecoli/ecoli_node_none.h b/libecoli/ecoli_node_none.h new file mode 100644 index 0000000..842f211 --- /dev/null +++ b/libecoli/ecoli_node_none.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +/** + * This node does not match anything + */ + +#ifndef ECOLI_NODE_ANY_ +#define ECOLI_NODE_ANY_ + +#endif diff --git a/libecoli/ecoli_node_once.c b/libecoli/ecoli_node_once.c new file mode 100644 index 0000000..6309878 --- /dev/null +++ b/libecoli/ecoli_node_once.c @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_once); + +struct ec_node_once { + struct ec_node gen; + struct ec_node *child; +}; + +static unsigned int +count_node(struct ec_parse *parse, const struct ec_node *node) +{ + struct ec_parse *child; + unsigned int count = 0; + + if (parse == NULL) + return 0; + + if (ec_parse_get_node(parse) == node) + count++; + + EC_PARSE_FOREACH_CHILD(child, parse) + count += count_node(child, node); + + return count; +} + +static int +ec_node_once_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_once *node = (struct ec_node_once *)gen_node; + unsigned int count; + + /* count the number of occurences of the node: if already parsed, + * do not match + */ + count = count_node(ec_parse_get_root(state), node->child); + if (count > 0) + return EC_PARSE_NOMATCH; + + return ec_node_parse_child(node->child, state, strvec); +} + +static int +ec_node_once_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_once *node = (struct ec_node_once *)gen_node; + struct ec_parse *parse = ec_comp_get_state(comp); + unsigned int count; + int ret; + + /* count the number of occurences of the node: if already parsed, + * do not match + */ + count = count_node(ec_parse_get_root(parse), node->child); + if (count > 0) + return 0; + + ret = ec_node_complete_child(node->child, comp, strvec); + if (ret < 0) + return ret; + + return 0; +} + +static void ec_node_once_free_priv(struct ec_node *gen_node) +{ + struct ec_node_once *node = (struct ec_node_once *)gen_node; + + ec_node_free(node->child); +} + +static size_t +ec_node_once_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_once *node = (struct ec_node_once *)gen_node; + + if (node->child) + return 1; + return 0; +} + +static int +ec_node_once_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_once *node = (struct ec_node_once *)gen_node; + + if (i >= 1) + return -1; + + *child = node->child; + *refs = 1; + return 0; +} + +static struct ec_node_type ec_node_once_type = { + .name = "once", + .parse = ec_node_once_parse, + .complete = ec_node_once_complete, + .size = sizeof(struct ec_node_once), + .free_priv = ec_node_once_free_priv, + .get_children_count = ec_node_once_get_children_count, + .get_child = ec_node_once_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_once_type); + +int ec_node_once_set(struct ec_node *gen_node, struct ec_node *child) +{ + struct ec_node_once *node = (struct ec_node_once *)gen_node; + + if (gen_node == NULL || child == NULL) { + errno = EINVAL; + goto fail; + } + + if (ec_node_check_type(gen_node, &ec_node_once_type) < 0) + goto fail; + + node->child = child; + + return 0; + +fail: + ec_node_free(child); + return -1; +} + +struct ec_node *ec_node_once(const char *id, struct ec_node *child) +{ + struct ec_node *gen_node = NULL; + + if (child == NULL) + return NULL; + + gen_node = ec_node_from_type(&ec_node_once_type, id); + if (gen_node == NULL) + goto fail; + + ec_node_once_set(gen_node, child); + child = NULL; + + return gen_node; + +fail: + ec_node_free(child); + return NULL; +} + +/* LCOV_EXCL_START */ +static int ec_node_once_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node_many(EC_NO_ID, + EC_NODE_OR(EC_NO_ID, + ec_node_once(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")), + ec_node_str(EC_NO_ID, "bar") + ), 0, 0 + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 0); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 0, "foox"); + + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "foo", "bar", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "f", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "b", EC_NODE_ENDLIST, + "bar", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "", EC_NODE_ENDLIST, + "bar", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "bar", "", EC_NODE_ENDLIST, + "foo", "bar", EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_once_test = { + .name = "node_once", + .test = ec_node_once_testcase, +}; + +EC_TEST_REGISTER(ec_node_once_test); diff --git a/libecoli/ecoli_node_once.h b/libecoli/ecoli_node_once.h new file mode 100644 index 0000000..a610a83 --- /dev/null +++ b/libecoli/ecoli_node_once.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_ONCE_ +#define ECOLI_NODE_ONCE_ + +#include + +/* This node behaves like its child, but prevent from parsing it several + * times. + * + * Example: + * many( + * or( + * once(str("foo")), + * str("bar"))) + * + * Matches: [], ["foo", "bar"], ["bar", "bar"], ["foo", "bar", "bar"], ... + * But not: ["foo", "foo"], ["foo", "bar", "foo"], ... + */ + +/* on error, child is *not* freed */ +struct ec_node *ec_node_once(const char *id, struct ec_node *child); + +/* on error, child is *not* freed */ +int ec_node_once_set(struct ec_node *node, struct ec_node *child); + +#endif diff --git a/libecoli/ecoli_node_option.c b/libecoli/ecoli_node_option.c new file mode 100644 index 0000000..ab4f352 --- /dev/null +++ b/libecoli/ecoli_node_option.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_option); + +struct ec_node_option { + struct ec_node gen; + struct ec_node *child; +}; + +static int +ec_node_option_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_option *node = (struct ec_node_option *)gen_node; + int ret; + + ret = ec_node_parse_child(node->child, state, strvec); + if (ret < 0) + return ret; + + if (ret == EC_PARSE_NOMATCH) + return 0; + + return ret; +} + +static int +ec_node_option_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_option *node = (struct ec_node_option *)gen_node; + + return ec_node_complete_child(node->child, comp, strvec); +} + +static void ec_node_option_free_priv(struct ec_node *gen_node) +{ + struct ec_node_option *node = (struct ec_node_option *)gen_node; + + ec_node_free(node->child); +} + +static size_t +ec_node_option_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_option *node = (struct ec_node_option *)gen_node; + + if (node->child) + return 1; + return 0; +} + +static int +ec_node_option_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_option *node = (struct ec_node_option *)gen_node; + + if (i >= 1) + return -1; + + *child = node->child; + *refs = 1; + return 0; +} + +static struct ec_node_type ec_node_option_type = { + .name = "option", + .parse = ec_node_option_parse, + .complete = ec_node_option_complete, + .size = sizeof(struct ec_node_option), + .free_priv = ec_node_option_free_priv, + .get_children_count = ec_node_option_get_children_count, + .get_child = ec_node_option_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_option_type); + +int ec_node_option_set(struct ec_node *gen_node, struct ec_node *child) +{ + struct ec_node_option *node = (struct ec_node_option *)gen_node; + + if (gen_node == NULL || child == NULL) { + errno = EINVAL; + goto fail; + } + + if (ec_node_check_type(gen_node, &ec_node_option_type) < 0) + goto fail; + + node->child = child; + + return 0; + +fail: + ec_node_free(child); + return -1; +} + +struct ec_node *ec_node_option(const char *id, struct ec_node *child) +{ + struct ec_node *gen_node = NULL; + + if (child == NULL) + goto fail; + + gen_node = ec_node_from_type(&ec_node_option_type, id); + if (gen_node == NULL) + goto fail; + + ec_node_option_set(gen_node, child); + child = NULL; + + return gen_node; + +fail: + ec_node_free(child); + return NULL; +} + +/* LCOV_EXCL_START */ +static int ec_node_option_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 0, "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 0); + ec_node_free(node); + + /* test completion */ + node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "f", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "b", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_option_test = { + .name = "node_option", + .test = ec_node_option_testcase, +}; + +EC_TEST_REGISTER(ec_node_option_test); diff --git a/libecoli/ecoli_node_option.h b/libecoli/ecoli_node_option.h new file mode 100644 index 0000000..9d67480 --- /dev/null +++ b/libecoli/ecoli_node_option.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_OPTION_ +#define ECOLI_NODE_OPTION_ + +#include + +struct ec_node *ec_node_option(const char *id, struct ec_node *node); +int ec_node_option_set(struct ec_node *gen_node, struct ec_node *child); + +#endif diff --git a/libecoli/ecoli_node_or.c b/libecoli/ecoli_node_or.c new file mode 100644 index 0000000..2bf8fc7 --- /dev/null +++ b/libecoli/ecoli_node_or.c @@ -0,0 +1,347 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_or); + +struct ec_node_or { + struct ec_node gen; + struct ec_node **table; + size_t len; +}; + +static int +ec_node_or_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_or *node = (struct ec_node_or *)gen_node; + unsigned int i; + int ret; + + for (i = 0; i < node->len; i++) { + ret = ec_node_parse_child(node->table[i], state, strvec); + if (ret == EC_PARSE_NOMATCH) + continue; + return ret; + } + + return EC_PARSE_NOMATCH; +} + +static int +ec_node_or_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_or *node = (struct ec_node_or *)gen_node; + int ret; + size_t n; + + for (n = 0; n < node->len; n++) { + ret = ec_node_complete_child(node->table[n], + comp, strvec); + if (ret < 0) + return ret; + } + + return 0; +} + +static void ec_node_or_free_priv(struct ec_node *gen_node) +{ + struct ec_node_or *node = (struct ec_node_or *)gen_node; + size_t i; + + for (i = 0; i < node->len; i++) + ec_node_free(node->table[i]); + ec_free(node->table); + node->table = NULL; + node->len = 0; +} + +static const struct ec_config_schema ec_node_or_subschema[] = { + { + .desc = "A child node which is part of the choice.", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static const struct ec_config_schema ec_node_or_schema[] = { + { + .key = "children", + .desc = "The list of children nodes defining the choice " + "elements.", + .type = EC_CONFIG_TYPE_LIST, + .subschema = ec_node_or_subschema, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_or_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_or *node = (struct ec_node_or *)gen_node; + struct ec_node **table = NULL; + size_t i, len = 0; + + table = ec_node_config_node_list_to_table( + ec_config_dict_get(config, "children"), &len); + if (table == NULL) + goto fail; + + for (i = 0; i < node->len; i++) + ec_node_free(node->table[i]); + ec_free(node->table); + node->table = table; + node->len = len; + + return 0; + +fail: + for (i = 0; i < len; i++) + ec_node_free(table[i]); + ec_free(table); + return -1; +} + +static size_t +ec_node_or_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_or *node = (struct ec_node_or *)gen_node; + return node->len; +} + +static int +ec_node_or_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_or *node = (struct ec_node_or *)gen_node; + + if (i >= node->len) + return -1; + + *child = node->table[i]; + /* each child node is referenced twice: once in the config and + * once in the node->table[] */ + *refs = 2; + return 0; +} + +static struct ec_node_type ec_node_or_type = { + .name = "or", + .schema = ec_node_or_schema, + .set_config = ec_node_or_set_config, + .parse = ec_node_or_parse, + .complete = ec_node_or_complete, + .size = sizeof(struct ec_node_or), + .free_priv = ec_node_or_free_priv, + .get_children_count = ec_node_or_get_children_count, + .get_child = ec_node_or_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_or_type); + +int ec_node_or_add(struct ec_node *gen_node, struct ec_node *child) +{ + struct ec_node_or *node = (struct ec_node_or *)gen_node; + const struct ec_config *cur_config = NULL; + struct ec_config *config = NULL, *children; + int ret; + + assert(node != NULL); + + /* XXX factorize this code in a helper */ + + if (ec_node_check_type(gen_node, &ec_node_or_type) < 0) + goto fail; + + cur_config = ec_node_get_config(gen_node); + if (cur_config == NULL) + config = ec_config_dict(); + else + config = ec_config_dup(cur_config); + if (config == NULL) + goto fail; + + children = ec_config_dict_get(config, "children"); + if (children == NULL) { + children = ec_config_list(); + if (children == NULL) + goto fail; + + if (ec_config_dict_set(config, "children", children) < 0) + goto fail; /* children list is freed on error */ + } + + if (ec_config_list_add(children, ec_config_node(child)) < 0) { + child = NULL; + goto fail; + } + + ret = ec_node_set_config(gen_node, config); + config = NULL; /* freed */ + if (ret < 0) + goto fail; + + return 0; + +fail: + ec_config_free(config); + ec_node_free(child); + return -1; +} + +struct ec_node *__ec_node_or(const char *id, ...) +{ + struct ec_config *config = NULL, *children = NULL; + struct ec_node *gen_node = NULL; + struct ec_node *child; + va_list ap; + int ret; + + va_start(ap, id); + child = va_arg(ap, struct ec_node *); + + gen_node = ec_node_from_type(&ec_node_or_type, id); + if (gen_node == NULL) + goto fail_free_children; + + config = ec_config_dict(); + if (config == NULL) + goto fail_free_children; + + children = ec_config_list(); + if (children == NULL) + goto fail_free_children; + + for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) { + if (child == NULL) + goto fail_free_children; + + if (ec_config_list_add(children, ec_config_node(child)) < 0) { + child = NULL; + goto fail_free_children; + } + } + + if (ec_config_dict_set(config, "children", children) < 0) { + children = NULL; /* freed */ + goto fail; + } + children = NULL; + + ret = ec_node_set_config(gen_node, config); + config = NULL; /* freed */ + if (ret < 0) + goto fail; + + va_end(ap); + + return gen_node; + +fail_free_children: + for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) + ec_node_free(child); +fail: + ec_node_free(gen_node); /* will also free added children */ + ec_config_free(children); + ec_config_free(config); + va_end(ap); + + return NULL; +} + +/* LCOV_EXCL_START */ +static int ec_node_or_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = EC_NODE_OR(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_str(EC_NO_ID, "bar") + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1, " "); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foox"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "toto"); + testres |= EC_TEST_CHECK_PARSE(node, -1, ""); + ec_node_free(node); + + /* test completion */ + node = EC_NODE_OR(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_str(EC_NO_ID, "bar"), + ec_node_str(EC_NO_ID, "bar2"), + ec_node_str(EC_NO_ID, "toto"), + ec_node_str(EC_NO_ID, "titi") + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "f", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "b", EC_NODE_ENDLIST, + "bar", "bar2", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "bar", EC_NODE_ENDLIST, + "bar", "bar2", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "t", EC_NODE_ENDLIST, + "toto", "titi", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "to", EC_NODE_ENDLIST, + "toto", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "x", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_or_test = { + .name = "node_or", + .test = ec_node_or_testcase, +}; + +EC_TEST_REGISTER(ec_node_or_test); diff --git a/libecoli/ecoli_node_or.h b/libecoli/ecoli_node_or.h new file mode 100644 index 0000000..db115b2 --- /dev/null +++ b/libecoli/ecoli_node_or.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_OR_ +#define ECOLI_NODE_OR_ + +#include + +#define EC_NODE_OR(args...) __ec_node_or(args, EC_NODE_ENDLIST) + +/* list must be terminated with EC_NODE_ENDLIST */ +/* all nodes given in the list will be freed when freeing this one */ +/* avoid using this function directly, prefer the macro EC_NODE_OR() or + * ec_node_or() + ec_node_or_add() */ +struct ec_node *__ec_node_or(const char *id, ...); + +struct ec_node *ec_node_or(const char *id); + +/* child is consumed */ +int ec_node_or_add(struct ec_node *node, struct ec_node *child); + + +#endif diff --git a/libecoli/ecoli_node_re.c b/libecoli/ecoli_node_re.c new file mode 100644 index 0000000..6ac5182 --- /dev/null +++ b/libecoli/ecoli_node_re.c @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_re); + +struct ec_node_re { + struct ec_node gen; + char *re_str; + regex_t re; +}; + +static int +ec_node_re_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_re *node = (struct ec_node_re *)gen_node; + const char *str; + regmatch_t pos; + + (void)state; + + if (ec_strvec_len(strvec) == 0) + return EC_PARSE_NOMATCH; + + str = ec_strvec_val(strvec, 0); + if (regexec(&node->re, str, 1, &pos, 0) != 0) + return EC_PARSE_NOMATCH; + if (pos.rm_so != 0 || pos.rm_eo != (int)strlen(str)) + return EC_PARSE_NOMATCH; + + return 1; +} + +static void ec_node_re_free_priv(struct ec_node *gen_node) +{ + struct ec_node_re *node = (struct ec_node_re *)gen_node; + + if (node->re_str != NULL) { + ec_free(node->re_str); + regfree(&node->re); + } +} + +static struct ec_node_type ec_node_re_type = { + .name = "re", + .parse = ec_node_re_parse, + .complete = ec_node_complete_unknown, + .size = sizeof(struct ec_node_re), + .free_priv = ec_node_re_free_priv, +}; + +EC_NODE_TYPE_REGISTER(ec_node_re_type); + +int ec_node_re_set_regexp(struct ec_node *gen_node, const char *str) +{ + struct ec_node_re *node = (struct ec_node_re *)gen_node; + char *str_copy = NULL; + regex_t re; + int ret; + + EC_CHECK_ARG(str != NULL, -1, EINVAL); + + str_copy = ec_strdup(str); + if (str_copy == NULL) + goto fail; + + ret = regcomp(&re, str_copy, REG_EXTENDED); + if (ret != 0) { + if (ret == REG_ESPACE) + errno = ENOMEM; + else + errno = EINVAL; + goto fail; + } + + if (node->re_str != NULL) { + ec_free(node->re_str); + regfree(&node->re); + } + node->re_str = str_copy; + node->re = re; + + return 0; + +fail: + ec_free(str_copy); + return -1; +} + +struct ec_node *ec_node_re(const char *id, const char *re_str) +{ + struct ec_node *gen_node = NULL; + + gen_node = ec_node_from_type(&ec_node_re_type, id); + if (gen_node == NULL) + goto fail; + + if (ec_node_re_set_regexp(gen_node, re_str) < 0) + goto fail; + + return gen_node; + +fail: + ec_node_free(gen_node); + return NULL; +} + +/* LCOV_EXCL_START */ +static int ec_node_re_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node_re(EC_NO_ID, "fo+|bar"); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar"); + testres |= EC_TEST_CHECK_PARSE(node, -1, " foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, ""); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_re_test = { + .name = "node_re", + .test = ec_node_re_testcase, +}; + +EC_TEST_REGISTER(ec_node_re_test); diff --git a/libecoli/ecoli_node_re.h b/libecoli/ecoli_node_re.h new file mode 100644 index 0000000..bc3a317 --- /dev/null +++ b/libecoli/ecoli_node_re.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_RE_ +#define ECOLI_NODE_RE_ + +#include + +struct ec_node *ec_node_re(const char *id, const char *str); + +/* re is duplicated */ +int ec_node_re_set_regexp(struct ec_node *node, const char *re); + +#endif diff --git a/libecoli/ecoli_node_re_lex.c b/libecoli/ecoli_node_re_lex.c new file mode 100644 index 0000000..f5a6c37 --- /dev/null +++ b/libecoli/ecoli_node_re_lex.c @@ -0,0 +1,308 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_re_lex); + +struct regexp_pattern { + char *pattern; + regex_t r; + bool keep; +}; + +struct ec_node_re_lex { + struct ec_node gen; + struct ec_node *child; + struct regexp_pattern *table; + size_t len; +}; + +static struct ec_strvec * +tokenize(struct regexp_pattern *table, size_t table_len, const char *str) +{ + struct ec_strvec *strvec = NULL; + char *dup = NULL; + char c; + size_t len, off = 0; + size_t i; + int ret; + regmatch_t pos; + + dup = ec_strdup(str); + if (dup == NULL) + goto fail; + + strvec = ec_strvec(); + if (strvec == NULL) + goto fail; + + len = strlen(dup); + while (off < len) { + for (i = 0; i < table_len; i++) { + ret = regexec(&table[i].r, &dup[off], 1, &pos, 0); + if (ret != 0) + continue; + if (pos.rm_so != 0 || pos.rm_eo == 0) { + ret = -1; + continue; + } + + if (table[i].keep == 0) + break; + + c = dup[pos.rm_eo + off]; + dup[pos.rm_eo + off] = '\0'; + EC_LOG(EC_LOG_DEBUG, "re_lex match <%s>\n", &dup[off]); + if (ec_strvec_add(strvec, &dup[off]) < 0) + goto fail; + + dup[pos.rm_eo + off] = c; + break; + } + + if (ret != 0) + goto fail; + + off += pos.rm_eo; + } + + ec_free(dup); + return strvec; + +fail: + ec_free(dup); + ec_strvec_free(strvec); + return NULL; +} + +static int +ec_node_re_lex_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; + struct ec_strvec *new_vec = NULL; + struct ec_parse *child_parse; + const char *str; + int ret; + + if (ec_strvec_len(strvec) == 0) { + new_vec = ec_strvec(); + } else { + str = ec_strvec_val(strvec, 0); + new_vec = tokenize(node->table, node->len, str); + } + if (new_vec == NULL) + goto fail; + + ret = ec_node_parse_child(node->child, state, new_vec); + if (ret < 0) + goto fail; + + if ((unsigned)ret == ec_strvec_len(new_vec)) { + ret = 1; + } else if (ret != EC_PARSE_NOMATCH) { + child_parse = ec_parse_get_last_child(state); + ec_parse_unlink_child(state, child_parse); + ec_parse_free(child_parse); + ret = EC_PARSE_NOMATCH; + } + + ec_strvec_free(new_vec); + new_vec = NULL; + + return ret; + + fail: + ec_strvec_free(new_vec); + return -1; +} + +static void ec_node_re_lex_free_priv(struct ec_node *gen_node) +{ + struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; + unsigned int i; + + ec_node_free(node->child); + for (i = 0; i < node->len; i++) { + ec_free(node->table[i].pattern); + regfree(&node->table[i].r); + } + + ec_free(node->table); +} + +static size_t +ec_node_re_lex_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; + + if (node->child) + return 1; + return 0; +} + +static int +ec_node_re_lex_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; + + if (i >= 1) + return -1; + + *child = node->child; + *refs = 1; + return 0; +} + +static struct ec_node_type ec_node_re_lex_type = { + .name = "re_lex", + .parse = ec_node_re_lex_parse, + .complete = ec_node_complete_unknown, + .size = sizeof(struct ec_node_re_lex), + .free_priv = ec_node_re_lex_free_priv, + .get_children_count = ec_node_re_lex_get_children_count, + .get_child = ec_node_re_lex_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_re_lex_type); + +int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep) +{ + struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; + struct regexp_pattern *table; + int ret; + char *pat_dup = NULL; + + pat_dup = ec_strdup(pattern); + if (pat_dup == NULL) + goto fail; + + table = ec_realloc(node->table, sizeof(*table) * (node->len + 1)); + if (table == NULL) + goto fail; + + ret = regcomp(&table[node->len].r, pattern, REG_EXTENDED); + if (ret != 0) { + EC_LOG(EC_LOG_ERR, + "Regular expression <%s> compilation failed: %d\n", + pattern, ret); + if (ret == REG_ESPACE) + errno = ENOMEM; + else + errno = EINVAL; + + goto fail; + } + + table[node->len].pattern = pat_dup; + table[node->len].keep = keep; + node->len++; + node->table = table; + + return 0; + +fail: + ec_free(pat_dup); + return -1; +} + +struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child) +{ + struct ec_node_re_lex *node = NULL; + + if (child == NULL) + return NULL; + + node = (struct ec_node_re_lex *)ec_node_from_type(&ec_node_re_lex_type, id); + if (node == NULL) { + ec_node_free(child); + return NULL; + } + + node->child = child; + + return &node->gen; +} + +/* LCOV_EXCL_START */ +static int ec_node_re_lex_testcase(void) +{ + struct ec_node *node; + int ret, testres = 0; + + node = ec_node_re_lex(EC_NO_ID, + ec_node_many(EC_NO_ID, + EC_NODE_OR(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_str(EC_NO_ID, "bar"), + ec_node_int(EC_NO_ID, 0, 1000, 0) + ), 0, 0 + ) + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + + ret = ec_node_re_lex_add(node, "[a-zA-Z]+", 1); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "[0-9]+", 1); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "=", 1); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "-", 1); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "\\+", 1); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "[ ]+", 0); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + if (ret != 0) { + EC_LOG(EC_LOG_ERR, "cannot add regexp to node\n"); + ec_node_free(node); + return -1; + } + + testres |= EC_TEST_CHECK_PARSE(node, 1, " foo bar 324 bar234"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar324"); + testres |= EC_TEST_CHECK_PARSE(node, 1, ""); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar"); + + /* no completion */ + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_re_lex_test = { + .name = "node_re_lex", + .test = ec_node_re_lex_testcase, +}; + +EC_TEST_REGISTER(ec_node_re_lex_test); diff --git a/libecoli/ecoli_node_re_lex.h b/libecoli/ecoli_node_re_lex.h new file mode 100644 index 0000000..632627c --- /dev/null +++ b/libecoli/ecoli_node_re_lex.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_RE_LEX_ +#define ECOLI_NODE_RE_LEX_ + +#include + +struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child); + +int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep); + +#endif diff --git a/libecoli/ecoli_node_seq.c b/libecoli/ecoli_node_seq.c new file mode 100644 index 0000000..ff0c5de --- /dev/null +++ b/libecoli/ecoli_node_seq.c @@ -0,0 +1,442 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_seq); + +struct ec_node_seq { + struct ec_node gen; + struct ec_node **table; + size_t len; +}; + +static int +ec_node_seq_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_seq *node = (struct ec_node_seq *)gen_node; + struct ec_strvec *childvec = NULL; + size_t len = 0; + unsigned int i; + int ret; + + for (i = 0; i < node->len; i++) { + childvec = ec_strvec_ndup(strvec, len, + ec_strvec_len(strvec) - len); + if (childvec == NULL) + goto fail; + + ret = ec_node_parse_child(node->table[i], state, childvec); + if (ret < 0) + goto fail; + + ec_strvec_free(childvec); + childvec = NULL; + + if (ret == EC_PARSE_NOMATCH) { + ec_parse_free_children(state); + return EC_PARSE_NOMATCH; + } + + len += ret; + } + + return len; + +fail: + ec_strvec_free(childvec); + return -1; +} + +static int +__ec_node_seq_complete(struct ec_node **table, size_t table_len, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_parse *parse = ec_comp_get_state(comp); + struct ec_strvec *childvec = NULL; + unsigned int i; + int ret; + + if (table_len == 0) + return 0; + + /* + * Example of completion for a sequence node = [n1,n2] and an + * input = [a,b,c,d]: + * + * result = complete(n1, [a,b,c,d]) + + * complete(n2, [b,c,d]) if n1 matches [a] + + * complete(n2, [c,d]) if n1 matches [a,b] + + * complete(n2, [d]) if n1 matches [a,b,c] + + * complete(n2, []) if n1 matches [a,b,c,d] + */ + + /* first, try to complete with the first node of the table */ + ret = ec_node_complete_child(table[0], comp, strvec); + if (ret < 0) + goto fail; + + /* then, if the first node of the table matches the beginning of the + * strvec, try to complete the rest */ + for (i = 0; i < ec_strvec_len(strvec); i++) { + childvec = ec_strvec_ndup(strvec, 0, i); + if (childvec == NULL) + goto fail; + + ret = ec_node_parse_child(table[0], parse, childvec); + if (ret < 0) + goto fail; + + ec_strvec_free(childvec); + childvec = NULL; + + if ((unsigned int)ret != i) { + if (ret != EC_PARSE_NOMATCH) + ec_parse_del_last_child(parse); + continue; + } + + childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i); + if (childvec == NULL) { + ec_parse_del_last_child(parse); + goto fail; + } + + ret = __ec_node_seq_complete(&table[1], + table_len - 1, + comp, childvec); + ec_parse_del_last_child(parse); + ec_strvec_free(childvec); + childvec = NULL; + + if (ret < 0) + goto fail; + } + + return 0; + +fail: + ec_strvec_free(childvec); + return -1; +} + +static int +ec_node_seq_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_seq *node = (struct ec_node_seq *)gen_node; + + return __ec_node_seq_complete(node->table, node->len, comp, + strvec); +} + +static void ec_node_seq_free_priv(struct ec_node *gen_node) +{ + struct ec_node_seq *node = (struct ec_node_seq *)gen_node; + size_t i; + + for (i = 0; i < node->len; i++) + ec_node_free(node->table[i]); + ec_free(node->table); + node->table = NULL; + node->len = 0; +} + +static const struct ec_config_schema ec_node_seq_subschema[] = { + { + .desc = "A child node which is part of the sequence.", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static const struct ec_config_schema ec_node_seq_schema[] = { + { + .key = "children", + .desc = "The list of children nodes, to be parsed in sequence.", + .type = EC_CONFIG_TYPE_LIST, + .subschema = ec_node_seq_subschema, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_seq_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_seq *node = (struct ec_node_seq *)gen_node; + struct ec_node **table = NULL; + size_t i, len = 0; + + table = ec_node_config_node_list_to_table( + ec_config_dict_get(config, "children"), &len); + if (table == NULL) + goto fail; + + for (i = 0; i < node->len; i++) + ec_node_free(node->table[i]); + ec_free(node->table); + node->table = table; + node->len = len; + + return 0; + +fail: + for (i = 0; i < len; i++) + ec_node_free(table[i]); + ec_free(table); + return -1; +} + +static size_t +ec_node_seq_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_seq *node = (struct ec_node_seq *)gen_node; + return node->len; +} + +static int +ec_node_seq_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_seq *node = (struct ec_node_seq *)gen_node; + + if (i >= node->len) + return -1; + + *child = node->table[i]; + /* each child node is referenced twice: once in the config and + * once in the node->table[] */ + *refs = 2; + return 0; +} + +static struct ec_node_type ec_node_seq_type = { + .name = "seq", + .schema = ec_node_seq_schema, + .set_config = ec_node_seq_set_config, + .parse = ec_node_seq_parse, + .complete = ec_node_seq_complete, + .size = sizeof(struct ec_node_seq), + .free_priv = ec_node_seq_free_priv, + .get_children_count = ec_node_seq_get_children_count, + .get_child = ec_node_seq_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_seq_type); + +int ec_node_seq_add(struct ec_node *gen_node, struct ec_node *child) +{ + struct ec_node_seq *node = (struct ec_node_seq *)gen_node; + const struct ec_config *cur_config = NULL; + struct ec_config *config = NULL, *children; + int ret; + + assert(node != NULL); + + /* XXX factorize this code in a helper */ + + if (ec_node_check_type(gen_node, &ec_node_seq_type) < 0) + goto fail; + + cur_config = ec_node_get_config(gen_node); + if (cur_config == NULL) + config = ec_config_dict(); + else + config = ec_config_dup(cur_config); + if (config == NULL) + goto fail; + + children = ec_config_dict_get(config, "children"); + if (children == NULL) { + children = ec_config_list(); + if (children == NULL) + goto fail; + + if (ec_config_dict_set(config, "children", children) < 0) + goto fail; /* children list is freed on error */ + } + + if (ec_config_list_add(children, ec_config_node(child)) < 0) { + child = NULL; + goto fail; + } + + ret = ec_node_set_config(gen_node, config); + config = NULL; /* freed */ + if (ret < 0) + goto fail; + + return 0; + +fail: + ec_config_free(config); + ec_node_free(child); + return -1; +} + +struct ec_node *__ec_node_seq(const char *id, ...) +{ + struct ec_config *config = NULL, *children = NULL; + struct ec_node *gen_node = NULL; + struct ec_node *child; + va_list ap; + int ret; + + va_start(ap, id); + child = va_arg(ap, struct ec_node *); + + gen_node = ec_node_from_type(&ec_node_seq_type, id); + if (gen_node == NULL) + goto fail_free_children; + + config = ec_config_dict(); + if (config == NULL) + goto fail_free_children; + + children = ec_config_list(); + if (children == NULL) + goto fail_free_children; + + for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) { + if (child == NULL) + goto fail_free_children; + + if (ec_config_list_add(children, ec_config_node(child)) < 0) { + child = NULL; + goto fail_free_children; + } + } + + if (ec_config_dict_set(config, "children", children) < 0) { + children = NULL; /* freed */ + goto fail; + } + children = NULL; + + ret = ec_node_set_config(gen_node, config); + config = NULL; /* freed */ + if (ret < 0) + goto fail; + + va_end(ap); + + return gen_node; + +fail_free_children: + for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) + ec_node_free(child); +fail: + ec_node_free(gen_node); /* will also free added children */ + ec_config_free(children); + ec_config_free(config); + va_end(ap); + + return NULL; +} + +/* LCOV_EXCL_START */ +static int ec_node_seq_testcase(void) +{ + struct ec_node *node = NULL; + int testres = 0; + + node = EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_str(EC_NO_ID, "bar") + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "toto"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foox", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "barx"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "bar", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "", "foo"); + + testres |= (ec_node_seq_add(node, ec_node_str(EC_NO_ID, "grr")) < 0); + testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "grr"); + + ec_node_free(node); + + /* test completion */ + node = EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "toto")), + ec_node_str(EC_NO_ID, "bar") + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "f", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "", EC_NODE_ENDLIST, + "bar", "toto", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "t", EC_NODE_ENDLIST, + "toto", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "b", EC_NODE_ENDLIST, + "bar", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", "bar", EC_NODE_ENDLIST, + "bar", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "x", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foobarx", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_seq_test = { + .name = "node_seq", + .test = ec_node_seq_testcase, +}; + +EC_TEST_REGISTER(ec_node_seq_test); diff --git a/libecoli/ecoli_node_seq.h b/libecoli/ecoli_node_seq.h new file mode 100644 index 0000000..21c96b1 --- /dev/null +++ b/libecoli/ecoli_node_seq.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_SEQ_ +#define ECOLI_NODE_SEQ_ + +#include + +#define EC_NODE_SEQ(args...) __ec_node_seq(args, EC_NODE_ENDLIST) + +/* list must be terminated with EC_NODE_ENDLIST */ +/* all nodes given in the list will be freed when freeing this one */ +/* avoid using this function directly, prefer the macro EC_NODE_SEQ() or + * ec_node_seq() + ec_node_seq_add() */ +struct ec_node *__ec_node_seq(const char *id, ...); + +struct ec_node *ec_node_seq(const char *id); + +/* child is consumed */ +int ec_node_seq_add(struct ec_node *node, struct ec_node *child); + +#endif diff --git a/libecoli/ecoli_node_sh_lex.c b/libecoli/ecoli_node_sh_lex.c new file mode 100644 index 0000000..e27f21b --- /dev/null +++ b/libecoli/ecoli_node_sh_lex.c @@ -0,0 +1,491 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_sh_lex); + +struct ec_node_sh_lex { + struct ec_node gen; + struct ec_node *child; +}; + +static size_t eat_spaces(const char *str) +{ + size_t i = 0; + + /* skip spaces */ + while (isblank(str[i])) + i++; + + return i; +} + +/* + * Allocate a new string which is a copy of the input string with quotes + * removed. If quotes are not closed properly, set missing_quote to the + * missing quote char. + */ +static char *unquote_str(const char *str, size_t n, int allow_missing_quote, + char *missing_quote) +{ + unsigned s = 1, d = 0; + char quote = str[0]; + char *dst; + int closed = 0; + + dst = ec_malloc(n); + if (dst == NULL) { + errno = ENOMEM; + return NULL; + } + + /* copy string and remove quotes */ + while (s < n && d < n && str[s] != '\0') { + if (str[s] == '\\' && str[s+1] == quote) { + dst[d++] = quote; + s += 2; + continue; + } + if (str[s] == '\\' && str[s+1] == '\\') { + dst[d++] = '\\'; + s += 2; + continue; + } + if (str[s] == quote) { + s++; + closed = 1; + break; + } + dst[d++] = str[s++]; + } + + /* not enough room in dst buffer (should not happen) */ + if (d >= n) { + ec_free(dst); + errno = EMSGSIZE; + return NULL; + } + + /* quote not closed */ + if (closed == 0) { + if (missing_quote != NULL) + *missing_quote = str[0]; + if (allow_missing_quote == 0) { + ec_free(dst); + errno = EBADMSG; + return NULL; + } + } + dst[d++] = '\0'; + + return dst; +} + +static size_t eat_quoted_str(const char *str) +{ + size_t i = 0; + char quote = str[0]; + + while (str[i] != '\0') { + if (str[i] != '\\' && str[i+1] == quote) + return i + 2; + i++; + } + + /* unclosed quote, will be detected later */ + return i; +} + +static size_t eat_str(const char *str) +{ + size_t i = 0; + + /* eat chars until we find a quote, space, or end of string */ + while (!isblank(str[i]) && str[i] != '\0' && + str[i] != '"' && str[i] != '\'') + i++; + + return i; +} + +static struct ec_strvec *tokenize(const char *str, int completion, + int allow_missing_quote, char *missing_quote) +{ + struct ec_strvec *strvec = NULL; + size_t off = 0, len, suboff, sublen; + char *word = NULL, *concat = NULL, *tmp; + int last_is_space = 1; + + strvec = ec_strvec(); + if (strvec == NULL) + goto fail; + + while (str[off] != '\0') { + if (missing_quote != NULL) + *missing_quote = '\0'; + len = eat_spaces(&str[off]); + if (len > 0) + last_is_space = 1; + off += len; + + len = 0; + suboff = off; + while (str[suboff] != '\0') { + if (missing_quote != NULL) + *missing_quote = '\0'; + last_is_space = 0; + if (str[suboff] == '"' || str[suboff] == '\'') { + sublen = eat_quoted_str(&str[suboff]); + word = unquote_str(&str[suboff], sublen, + allow_missing_quote, missing_quote); + } else { + sublen = eat_str(&str[suboff]); + if (sublen == 0) + break; + word = ec_strndup(&str[suboff], sublen); + } + + if (word == NULL) + goto fail; + + len += sublen; + suboff += sublen; + + if (concat == NULL) { + concat = word; + word = NULL; + } else { + tmp = ec_realloc(concat, len + 1); + if (tmp == NULL) + goto fail; + concat = tmp; + strcat(concat, word); + ec_free(word); + word = NULL; + } + } + + if (concat != NULL) { + if (ec_strvec_add(strvec, concat) < 0) + goto fail; + ec_free(concat); + concat = NULL; + } + + off += len; + } + + /* in completion mode, append an empty string in the vector if + * the input string ends with space */ + if (completion && last_is_space) { + if (ec_strvec_add(strvec, "") < 0) + goto fail; + } + + return strvec; + + fail: + ec_free(word); + ec_free(concat); + ec_strvec_free(strvec); + return NULL; +} + +static int +ec_node_sh_lex_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; + struct ec_strvec *new_vec = NULL; + struct ec_parse *child_parse; + const char *str; + int ret; + + if (ec_strvec_len(strvec) == 0) { + new_vec = ec_strvec(); + } else { + str = ec_strvec_val(strvec, 0); + new_vec = tokenize(str, 0, 0, NULL); + } + if (new_vec == NULL && errno == EBADMSG) /* quotes not closed */ + return EC_PARSE_NOMATCH; + if (new_vec == NULL) + goto fail; + + ret = ec_node_parse_child(node->child, state, new_vec); + if (ret < 0) + goto fail; + + if ((unsigned)ret == ec_strvec_len(new_vec)) { + ret = 1; + } else if (ret != EC_PARSE_NOMATCH) { + child_parse = ec_parse_get_last_child(state); + ec_parse_unlink_child(state, child_parse); + ec_parse_free(child_parse); + ret = EC_PARSE_NOMATCH; + } + + ec_strvec_free(new_vec); + new_vec = NULL; + + return ret; + + fail: + ec_strvec_free(new_vec); + return -1; +} + +static int +ec_node_sh_lex_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; + struct ec_comp *tmp_comp = NULL; + struct ec_strvec *new_vec = NULL; + struct ec_comp_iter *iter = NULL; + struct ec_comp_item *item = NULL; + char *new_str = NULL; + const char *str; + char missing_quote = '\0'; + int ret; + + if (ec_strvec_len(strvec) != 1) + return 0; + + str = ec_strvec_val(strvec, 0); + new_vec = tokenize(str, 1, 1, &missing_quote); + if (new_vec == NULL) + goto fail; + + /* we will store the completions in a temporary struct, because + * we want to update them (ex: add missing quotes) */ + tmp_comp = ec_comp(ec_comp_get_state(comp)); + if (tmp_comp == NULL) + goto fail; + + ret = ec_node_complete_child(node->child, tmp_comp, new_vec); + if (ret < 0) + goto fail; + + /* add missing quote for full completions */ + if (missing_quote != '\0') { + iter = ec_comp_iter(tmp_comp, EC_COMP_FULL); + if (iter == NULL) + goto fail; + while ((item = ec_comp_iter_next(iter)) != NULL) { + str = ec_comp_item_get_str(item); + if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str, + missing_quote) < 0) { + new_str = NULL; + goto fail; + } + if (ec_comp_item_set_str(item, new_str) < 0) + goto fail; + ec_free(new_str); + new_str = NULL; + + str = ec_comp_item_get_completion(item); + if (ec_asprintf(&new_str, "%s%c", str, + missing_quote) < 0) { + new_str = NULL; + goto fail; + } + if (ec_comp_item_set_completion(item, new_str) < 0) + goto fail; + ec_free(new_str); + new_str = NULL; + } + } + + ec_comp_iter_free(iter); + ec_strvec_free(new_vec); + + ec_comp_merge(comp, tmp_comp); + + return 0; + + fail: + ec_comp_free(tmp_comp); + ec_comp_iter_free(iter); + ec_strvec_free(new_vec); + ec_free(new_str); + + return -1; +} + +static void ec_node_sh_lex_free_priv(struct ec_node *gen_node) +{ + struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; + + ec_node_free(node->child); +} + +static size_t +ec_node_sh_lex_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; + + if (node->child) + return 1; + return 0; +} + +static int +ec_node_sh_lex_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node; + + if (i >= 1) + return -1; + + *refs = 1; + *child = node->child; + return 0; +} + +static struct ec_node_type ec_node_sh_lex_type = { + .name = "sh_lex", + .parse = ec_node_sh_lex_parse, + .complete = ec_node_sh_lex_complete, + .size = sizeof(struct ec_node_sh_lex), + .free_priv = ec_node_sh_lex_free_priv, + .get_children_count = ec_node_sh_lex_get_children_count, + .get_child = ec_node_sh_lex_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type); + +struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child) +{ + struct ec_node_sh_lex *node = NULL; + + if (child == NULL) + return NULL; + + node = (struct ec_node_sh_lex *)ec_node_from_type(&ec_node_sh_lex_type, id); + if (node == NULL) { + ec_node_free(child); + return NULL; + } + + node->child = child; + + return &node->gen; +} + +/* LCOV_EXCL_START */ +static int ec_node_sh_lex_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node_sh_lex(EC_NO_ID, + EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_option(EC_NO_ID, + ec_node_str(EC_NO_ID, "toto") + ), + ec_node_str(EC_NO_ID, "bar") + ) + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar"); + testres |= EC_TEST_CHECK_PARSE(node, 1, " foo bar"); + testres |= EC_TEST_CHECK_PARSE(node, 1, " 'foo' \"bar\""); + testres |= EC_TEST_CHECK_PARSE(node, 1, " 'f'oo 'toto' bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1, " foo toto bar'"); + ec_node_free(node); + + /* test completion */ + node = ec_node_sh_lex(EC_NO_ID, + EC_NODE_SEQ(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_option(EC_NO_ID, + ec_node_str(EC_NO_ID, "toto") + ), + ec_node_str(EC_NO_ID, "bar"), + ec_node_str(EC_NO_ID, "titi") + ) + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + " ", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "f", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo ", EC_NODE_ENDLIST, + "bar", "toto", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo t", EC_NODE_ENDLIST, + "toto", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo b", EC_NODE_ENDLIST, + "bar", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo bar", EC_NODE_ENDLIST, + "bar", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo bar ", EC_NODE_ENDLIST, + "titi", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo toto bar ", EC_NODE_ENDLIST, + "titi", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "x", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo barx", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo 'b", EC_NODE_ENDLIST, + "'bar'", EC_NODE_ENDLIST); + + ec_node_free(node); + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_sh_lex_test = { + .name = "node_sh_lex", + .test = ec_node_sh_lex_testcase, +}; + +EC_TEST_REGISTER(ec_node_sh_lex_test); diff --git a/libecoli/ecoli_node_sh_lex.h b/libecoli/ecoli_node_sh_lex.h new file mode 100644 index 0000000..d45b998 --- /dev/null +++ b/libecoli/ecoli_node_sh_lex.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_SHLEX_ +#define ECOLI_NODE_SHLEX_ + +#include + +struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child); + +#endif diff --git a/libecoli/ecoli_node_space.c b/libecoli/ecoli_node_space.c new file mode 100644 index 0000000..761ed76 --- /dev/null +++ b/libecoli/ecoli_node_space.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_space); + +struct ec_node_space { + struct ec_node gen; +}; + +static int +ec_node_space_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + const char *str; + size_t len = 0; + + (void)state; + (void)gen_node; + + if (ec_strvec_len(strvec) == 0) + return EC_PARSE_NOMATCH; + + str = ec_strvec_val(strvec, 0); + while (isspace(str[len])) + len++; + if (len == 0 || len != strlen(str)) + return EC_PARSE_NOMATCH; + + return 1; +} + +static struct ec_node_type ec_node_space_type = { + .name = "space", + .parse = ec_node_space_parse, + .complete = ec_node_complete_unknown, + .size = sizeof(struct ec_node_space), +}; + +EC_NODE_TYPE_REGISTER(ec_node_space_type); + +/* LCOV_EXCL_START */ +static int ec_node_space_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node("space", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, " "); + testres |= EC_TEST_CHECK_PARSE(node, 1, " ", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, ""); + testres |= EC_TEST_CHECK_PARSE(node, -1, " foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foo "); + ec_node_free(node); + + /* test completion */ + node = ec_node("space", EC_NO_ID); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + /* never completes whatever the input */ + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + " ", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_space_test = { + .name = "space", + .test = ec_node_space_testcase, +}; + +EC_TEST_REGISTER(ec_node_space_test); diff --git a/libecoli/ecoli_node_space.h b/libecoli/ecoli_node_space.h new file mode 100644 index 0000000..0dd6202 --- /dev/null +++ b/libecoli/ecoli_node_space.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * This node matches one string in the vector if it is only composed of + * spaces, as interpreted by isspace(). + */ + +#ifndef ECOLI_NODE_SPACE_ +#define ECOLI_NODE_SPACE_ + +/* no API for now, since there is no specific configuration for this node */ + +#endif diff --git a/libecoli/ecoli_node_str.c b/libecoli/ecoli_node_str.c new file mode 100644 index 0000000..d53ea39 --- /dev/null +++ b/libecoli/ecoli_node_str.c @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_str); + +struct ec_node_str { + struct ec_node gen; + char *string; + unsigned len; +}; + +static int +ec_node_str_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_str *node = (struct ec_node_str *)gen_node; + const char *str; + + (void)state; + + if (node->string == NULL) { + errno = EINVAL; + return -1; + } + + if (ec_strvec_len(strvec) == 0) + return EC_PARSE_NOMATCH; + + str = ec_strvec_val(strvec, 0); + if (strcmp(str, node->string) != 0) + return EC_PARSE_NOMATCH; + + return 1; +} + +static int +ec_node_str_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_str *node = (struct ec_node_str *)gen_node; + const char *str; + size_t n = 0; + + if (ec_strvec_len(strvec) != 1) + return 0; + + str = ec_strvec_val(strvec, 0); + for (n = 0; n < node->len; n++) { + if (str[n] != node->string[n]) + break; + } + + /* no completion */ + if (str[n] != '\0') + return EC_PARSE_NOMATCH; + + if (ec_comp_add_item(comp, gen_node, NULL, EC_COMP_FULL, + str, node->string) < 0) + return -1; + + return 0; +} + +static const char *ec_node_str_desc(const struct ec_node *gen_node) +{ + struct ec_node_str *node = (struct ec_node_str *)gen_node; + + return node->string; +} + +static void ec_node_str_free_priv(struct ec_node *gen_node) +{ + struct ec_node_str *node = (struct ec_node_str *)gen_node; + + ec_free(node->string); +} + +static const struct ec_config_schema ec_node_str_schema[] = { + { + .key = "string", + .desc = "The string to match.", + .type = EC_CONFIG_TYPE_STRING, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_str_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_str *node = (struct ec_node_str *)gen_node; + const struct ec_config *value = NULL; + char *s = NULL; + + value = ec_config_dict_get(config, "string"); + if (value == NULL) { + errno = EINVAL; + goto fail; + } + + s = ec_strdup(value->string); + if (s == NULL) + goto fail; + + ec_free(node->string); + node->string = s; + node->len = strlen(node->string); + + return 0; + +fail: + ec_free(s); + return -1; +} + +static struct ec_node_type ec_node_str_type = { + .name = "str", + .schema = ec_node_str_schema, + .set_config = ec_node_str_set_config, + .parse = ec_node_str_parse, + .complete = ec_node_str_complete, + .desc = ec_node_str_desc, + .size = sizeof(struct ec_node_str), + .free_priv = ec_node_str_free_priv, +}; + +EC_NODE_TYPE_REGISTER(ec_node_str_type); + +int ec_node_str_set_str(struct ec_node *gen_node, const char *str) +{ + struct ec_config *config = NULL; + int ret; + + if (ec_node_check_type(gen_node, &ec_node_str_type) < 0) + goto fail; + + if (str == NULL) { + errno = EINVAL; + goto fail; + } + + config = ec_config_dict(); + if (config == NULL) + goto fail; + + ret = ec_config_dict_set(config, "string", ec_config_string(str)); + if (ret < 0) + goto fail; + + ret = ec_node_set_config(gen_node, config); + config = NULL; + if (ret < 0) + goto fail; + + return 0; + +fail: + ec_config_free(config); + return -1; +} + +struct ec_node *ec_node_str(const char *id, const char *str) +{ + struct ec_node *gen_node = NULL; + + gen_node = ec_node_from_type(&ec_node_str_type, id); + if (gen_node == NULL) + goto fail; + + if (ec_node_str_set_str(gen_node, str) < 0) + goto fail; + + return gen_node; + +fail: + ec_node_free(gen_node); + return NULL; +} + +/* LCOV_EXCL_START */ +static int ec_node_str_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = ec_node_str(EC_NO_ID, "foo"); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK(!strcmp(ec_node_desc(node), "foo"), + "Invalid node description."); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar"); + testres |= EC_TEST_CHECK_PARSE(node, -1, " foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, ""); + ec_node_free(node); + + node = ec_node_str(EC_NO_ID, "Здравствуйте"); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте", + "John!"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, ""); + ec_node_free(node); + + /* an empty string node always matches */ + node = ec_node_str(EC_NO_ID, ""); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 1, ""); + testres |= EC_TEST_CHECK_PARSE(node, 1, "", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, -1, "foo"); + ec_node_free(node); + + /* test completion */ + node = ec_node_str(EC_NO_ID, "foo"); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "f", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "foo", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "x", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_str_test = { + .name = "node_str", + .test = ec_node_str_testcase, +}; + +EC_TEST_REGISTER(ec_node_str_test); diff --git a/libecoli/ecoli_node_str.h b/libecoli/ecoli_node_str.h new file mode 100644 index 0000000..8a8634f --- /dev/null +++ b/libecoli/ecoli_node_str.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_STR_ +#define ECOLI_NODE_STR_ + +#include + +struct ec_node *ec_node_str(const char *id, const char *str); + +/* str is duplicated */ +int ec_node_str_set_str(struct ec_node *node, const char *str); + +#endif diff --git a/libecoli/ecoli_node_subset.c b/libecoli/ecoli_node_subset.c new file mode 100644 index 0000000..e3184ef --- /dev/null +++ b/libecoli/ecoli_node_subset.c @@ -0,0 +1,430 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(node_subset); + +struct ec_node_subset { + struct ec_node gen; + struct ec_node **table; + unsigned int len; +}; + +struct parse_result { + size_t parse_len; /* number of parsed nodes */ + size_t len; /* consumed strings */ +}; + +/* recursively find the longest list of nodes that matches: the state is + * updated accordingly. */ +static int +__ec_node_subset_parse(struct parse_result *out, struct ec_node **table, + size_t table_len, struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node **child_table; + struct ec_strvec *childvec = NULL; + size_t i, j, len = 0; + struct parse_result best_result, result; + struct ec_parse *best_parse = NULL; + int ret; + + if (table_len == 0) + return 0; + + memset(&best_result, 0, sizeof(best_result)); + + child_table = ec_calloc(table_len - 1, sizeof(*child_table)); + if (child_table == NULL) + goto fail; + + for (i = 0; i < table_len; i++) { + /* try to parse elt i */ + ret = ec_node_parse_child(table[i], state, strvec); + if (ret < 0) + goto fail; + + if (ret == EC_PARSE_NOMATCH) + continue; + + /* build a new table without elt i */ + for (j = 0; j < table_len; j++) { + if (j < i) + child_table[j] = table[j]; + else if (j > i) + child_table[j - 1] = table[j]; + } + + /* build a new strvec (ret is the len of matched strvec) */ + len = ret; + childvec = ec_strvec_ndup(strvec, len, + ec_strvec_len(strvec) - len); + if (childvec == NULL) + goto fail; + + memset(&result, 0, sizeof(result)); + ret = __ec_node_subset_parse(&result, child_table, + table_len - 1, state, childvec); + ec_strvec_free(childvec); + childvec = NULL; + if (ret < 0) + goto fail; + + /* if result is not the best, ignore */ + if (result.parse_len < best_result.parse_len) { + memset(&result, 0, sizeof(result)); + ec_parse_del_last_child(state); + continue; + } + + /* replace the previous best result */ + ec_parse_free(best_parse); + best_parse = ec_parse_get_last_child(state); + ec_parse_unlink_child(state, best_parse); + + best_result.parse_len = result.parse_len + 1; + best_result.len = len + result.len; + + memset(&result, 0, sizeof(result)); + } + + *out = best_result; + ec_free(child_table); + if (best_parse != NULL) + ec_parse_link_child(state, best_parse); + + return 0; + + fail: + ec_parse_free(best_parse); + ec_strvec_free(childvec); + ec_free(child_table); + return -1; +} + +static int +ec_node_subset_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_subset *node = (struct ec_node_subset *)gen_node; + struct ec_parse *parse = NULL; + struct parse_result result; + int ret; + + memset(&result, 0, sizeof(result)); + + ret = __ec_node_subset_parse(&result, node->table, + node->len, state, strvec); + if (ret < 0) + goto fail; + + /* if no child node matches, return a matching empty strvec */ + if (result.parse_len == 0) + return 0; + + return result.len; + + fail: + ec_parse_free(parse); + return ret; +} + +static int +__ec_node_subset_complete(struct ec_node **table, size_t table_len, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_parse *parse = ec_comp_get_state(comp); + struct ec_strvec *childvec = NULL; + struct ec_node *save; + size_t i, len; + int ret; + + /* + * example with table = [a, b, c] + * subset_complete([a,b,c], strvec) returns: + * complete(a, strvec) + complete(b, strvec) + complete(c, strvec) + + * + __subset_complete([b, c], childvec) if a matches + * + __subset_complete([a, c], childvec) if b matches + * + __subset_complete([a, b], childvec) if c matches + */ + + /* first, try to complete with each node of the table */ + for (i = 0; i < table_len; i++) { + if (table[i] == NULL) + continue; + + ret = ec_node_complete_child(table[i], + comp, strvec); + if (ret < 0) + goto fail; + } + + /* then, if a node matches, advance in strvec and try to complete with + * all the other nodes */ + for (i = 0; i < table_len; i++) { + if (table[i] == NULL) + continue; + + ret = ec_node_parse_child(table[i], parse, strvec); + if (ret < 0) + goto fail; + + if (ret == EC_PARSE_NOMATCH) + continue; + + len = ret; + childvec = ec_strvec_ndup(strvec, len, + ec_strvec_len(strvec) - len); + if (childvec == NULL) { + ec_parse_del_last_child(parse); + goto fail; + } + + save = table[i]; + table[i] = NULL; + ret = __ec_node_subset_complete(table, table_len, + comp, childvec); + table[i] = save; + ec_strvec_free(childvec); + childvec = NULL; + ec_parse_del_last_child(parse); + + if (ret < 0) + goto fail; + } + + return 0; + +fail: + return -1; +} + +static int +ec_node_subset_complete(const struct ec_node *gen_node, + struct ec_comp *comp, + const struct ec_strvec *strvec) +{ + struct ec_node_subset *node = (struct ec_node_subset *)gen_node; + + return __ec_node_subset_complete(node->table, node->len, comp, + strvec); +} + +static void ec_node_subset_free_priv(struct ec_node *gen_node) +{ + struct ec_node_subset *node = (struct ec_node_subset *)gen_node; + size_t i; + + for (i = 0; i < node->len; i++) + ec_node_free(node->table[i]); + ec_free(node->table); +} + +static size_t +ec_node_subset_get_children_count(const struct ec_node *gen_node) +{ + struct ec_node_subset *node = (struct ec_node_subset *)gen_node; + return node->len; +} + +static int +ec_node_subset_get_child(const struct ec_node *gen_node, size_t i, + struct ec_node **child, unsigned int *refs) +{ + struct ec_node_subset *node = (struct ec_node_subset *)gen_node; + + if (i >= node->len) + return -1; + + *child = node->table[i]; + *refs = 1; + return 0; +} + +static struct ec_node_type ec_node_subset_type = { + .name = "subset", + .parse = ec_node_subset_parse, + .complete = ec_node_subset_complete, + .size = sizeof(struct ec_node_subset), + .free_priv = ec_node_subset_free_priv, + .get_children_count = ec_node_subset_get_children_count, + .get_child = ec_node_subset_get_child, +}; + +EC_NODE_TYPE_REGISTER(ec_node_subset_type); + +int ec_node_subset_add(struct ec_node *gen_node, struct ec_node *child) +{ + struct ec_node_subset *node = (struct ec_node_subset *)gen_node; + struct ec_node **table; + + assert(node != NULL); // XXX specific assert for it, like in libyang + + if (child == NULL) { + errno = EINVAL; + goto fail; + } + + if (ec_node_check_type(gen_node, &ec_node_subset_type) < 0) + goto fail; + + table = ec_realloc(node->table, (node->len + 1) * sizeof(*node->table)); + if (table == NULL) { + ec_node_free(child); + return -1; + } + + node->table = table; + table[node->len] = child; + node->len++; + + return 0; + +fail: + ec_node_free(child); + return -1; +} + +struct ec_node *__ec_node_subset(const char *id, ...) +{ + struct ec_node *gen_node = NULL; + struct ec_node_subset *node = NULL; + struct ec_node *child; + va_list ap; + int fail = 0; + + va_start(ap, id); + + gen_node = ec_node_from_type(&ec_node_subset_type, id); + node = (struct ec_node_subset *)gen_node; + if (node == NULL) + fail = 1;; + + for (child = va_arg(ap, struct ec_node *); + child != EC_NODE_ENDLIST; + child = va_arg(ap, struct ec_node *)) { + + /* on error, don't quit the loop to avoid leaks */ + if (fail == 1 || child == NULL || + ec_node_subset_add(gen_node, child) < 0) { + fail = 1; + ec_node_free(child); + } + } + + if (fail == 1) + goto fail; + + va_end(ap); + return gen_node; + +fail: + ec_node_free(gen_node); /* will also free children */ + va_end(ap); + return NULL; +} + +/* LCOV_EXCL_START */ +static int ec_node_subset_testcase(void) +{ + struct ec_node *node; + int testres = 0; + + node = EC_NODE_SUBSET(EC_NO_ID, + EC_NODE_OR(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_str(EC_NO_ID, "bar")), + ec_node_str(EC_NO_ID, "bar"), + ec_node_str(EC_NO_ID, "toto") + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_PARSE(node, 0); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "titi"); + testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "toto"); + testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "bar"); + testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo"); + testres |= EC_TEST_CHECK_PARSE(node, 0, " "); + testres |= EC_TEST_CHECK_PARSE(node, 0, "foox"); + ec_node_free(node); + + /* test completion */ + node = EC_NODE_SUBSET(EC_NO_ID, + ec_node_str(EC_NO_ID, "foo"), + ec_node_str(EC_NO_ID, "bar"), + ec_node_str(EC_NO_ID, "bar2"), + ec_node_str(EC_NO_ID, "toto"), + ec_node_str(EC_NO_ID, "titi") + ); + if (node == NULL) { + EC_LOG(EC_LOG_ERR, "cannot create node\n"); + return -1; + } + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "", EC_NODE_ENDLIST, + "bar2", "bar", "foo", "toto", "titi", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "bar", "bar2", "", EC_NODE_ENDLIST, + "foo", "toto", "titi", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "f", EC_NODE_ENDLIST, + "foo", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "b", EC_NODE_ENDLIST, + "bar", "bar2", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "bar", EC_NODE_ENDLIST, + "bar", "bar2", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "bar", "b", EC_NODE_ENDLIST, + "bar2", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "t", EC_NODE_ENDLIST, + "toto", "titi", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "to", EC_NODE_ENDLIST, + "toto", EC_NODE_ENDLIST); + testres |= EC_TEST_CHECK_COMPLETE(node, + "x", EC_NODE_ENDLIST, + EC_NODE_ENDLIST); + ec_node_free(node); + + return testres; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_subset_test = { + .name = "node_subset", + .test = ec_node_subset_testcase, +}; + +EC_TEST_REGISTER(ec_node_subset_test); diff --git a/libecoli/ecoli_node_subset.h b/libecoli/ecoli_node_subset.h new file mode 100644 index 0000000..734b1ae --- /dev/null +++ b/libecoli/ecoli_node_subset.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_NODE_SUBSET_ +#define ECOLI_NODE_SUBSET_ + +#include + +#define EC_NODE_SUBSET(args...) __ec_node_subset(args, EC_NODE_ENDLIST) + +/* list must be terminated with EC_NODE_ENDLIST */ +/* all nodes given in the list will be freed when freeing this one */ +/* avoid using this function directly, prefer the macro EC_NODE_SUBSET() or + * ec_node_subset() + ec_node_subset_add() */ +struct ec_node *__ec_node_subset(const char *id, ...); + +struct ec_node *ec_node_subset(const char *id); + +/* child is consumed */ +int ec_node_subset_add(struct ec_node *node, struct ec_node *child); + +#endif diff --git a/libecoli/ecoli_parse.c b/libecoli/ecoli_parse.c new file mode 100644 index 0000000..6396fc1 --- /dev/null +++ b/libecoli/ecoli_parse.c @@ -0,0 +1,540 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(parse); + +TAILQ_HEAD(ec_parse_list, ec_parse); + +struct ec_parse { + TAILQ_ENTRY(ec_parse) next; + struct ec_parse_list children; + struct ec_parse *parent; + const struct ec_node *node; + struct ec_strvec *strvec; + struct ec_keyval *attrs; +}; + +static int __ec_node_parse_child(const struct ec_node *node, + struct ec_parse *state, + bool is_root, const struct ec_strvec *strvec) +{ + struct ec_strvec *match_strvec; + struct ec_parse *child = NULL; + int ret; + + if (ec_node_type(node)->parse == NULL) { + errno = ENOTSUP; + return -1; + } + + if (!is_root) { + child = ec_parse(node); + if (child == NULL) + return -1; + + ec_parse_link_child(state, child); + } else { + child = state; + } + ret = ec_node_type(node)->parse(node, child, strvec); + if (ret < 0) + goto fail; + + if (ret == EC_PARSE_NOMATCH) { + if (!is_root) { + ec_parse_unlink_child(state, child); + ec_parse_free(child); + } + return ret; + } + + match_strvec = ec_strvec_ndup(strvec, 0, ret); + if (match_strvec == NULL) + goto fail; + + child->strvec = match_strvec; + + return ret; + +fail: + if (!is_root) { + ec_parse_unlink_child(state, child); + ec_parse_free(child); + } + return -1; +} + +int ec_node_parse_child(const struct ec_node *node, struct ec_parse *state, + const struct ec_strvec *strvec) +{ + assert(state != NULL); + return __ec_node_parse_child(node, state, false, strvec); +} + +struct ec_parse *ec_node_parse_strvec(const struct ec_node *node, + const struct ec_strvec *strvec) +{ + struct ec_parse *parse = ec_parse(node); + int ret; + + if (parse == NULL) + return NULL; + + ret = __ec_node_parse_child(node, parse, true, strvec); + if (ret < 0) { + ec_parse_free(parse); + return NULL; + } + + return parse; +} + +struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str) +{ + struct ec_strvec *strvec = NULL; + struct ec_parse *parse = NULL; + + errno = ENOMEM; + strvec = ec_strvec(); + if (strvec == NULL) + goto fail; + + if (ec_strvec_add(strvec, str) < 0) + goto fail; + + parse = ec_node_parse_strvec(node, strvec); + if (parse == NULL) + goto fail; + + ec_strvec_free(strvec); + return parse; + + fail: + ec_strvec_free(strvec); + ec_parse_free(parse); + return NULL; +} + +struct ec_parse *ec_parse(const struct ec_node *node) +{ + struct ec_parse *parse = NULL; + + parse = ec_calloc(1, sizeof(*parse)); + if (parse == NULL) + goto fail; + + TAILQ_INIT(&parse->children); + + parse->node = node; + parse->attrs = ec_keyval(); + if (parse->attrs == NULL) + goto fail; + + return parse; + + fail: + if (parse != NULL) + ec_keyval_free(parse->attrs); + ec_free(parse); + + return NULL; +} + +static struct ec_parse * +__ec_parse_dup(const struct ec_parse *root, const struct ec_parse *ref, + struct ec_parse **new_ref) +{ + struct ec_parse *dup = NULL; + struct ec_parse *child, *dup_child; + struct ec_keyval *attrs = NULL; + + if (root == NULL) + return NULL; + + dup = ec_parse(root->node); + if (dup == NULL) + return NULL; + + if (root == ref) + *new_ref = dup; + + attrs = ec_keyval_dup(root->attrs); + if (attrs == NULL) + goto fail; + ec_keyval_free(dup->attrs); + dup->attrs = attrs; + + if (root->strvec != NULL) { + dup->strvec = ec_strvec_dup(root->strvec); + if (dup->strvec == NULL) + goto fail; + } + + TAILQ_FOREACH(child, &root->children, next) { + dup_child = __ec_parse_dup(child, ref, new_ref); + if (dup_child == NULL) + goto fail; + ec_parse_link_child(dup, dup_child); + } + + return dup; + +fail: + ec_parse_free(dup); + return NULL; +} + +struct ec_parse *ec_parse_dup(const struct ec_parse *parse) +{ + const struct ec_parse *root; + struct ec_parse *dup_root, *dup = NULL; + + root = ec_parse_get_root(parse); + dup_root = __ec_parse_dup(root, parse, &dup); + if (dup_root == NULL) + return NULL; + assert(dup != NULL); + + return dup; +} + +void ec_parse_free_children(struct ec_parse *parse) +{ + struct ec_parse *child; + + if (parse == NULL) + return; + + while (!TAILQ_EMPTY(&parse->children)) { + child = TAILQ_FIRST(&parse->children); + TAILQ_REMOVE(&parse->children, child, next); + child->parent = NULL; + ec_parse_free(child); + } +} + +void ec_parse_free(struct ec_parse *parse) +{ + if (parse == NULL) + return; + + ec_assert_print(parse->parent == NULL, + "parent not NULL in ec_parse_free()"); + + ec_parse_free_children(parse); + ec_strvec_free(parse->strvec); + ec_keyval_free(parse->attrs); + ec_free(parse); +} + +static void __ec_parse_dump(FILE *out, + const struct ec_parse *parse, size_t indent) +{ + struct ec_parse *child; + const struct ec_strvec *vec; + const char *id = "none", *typename = "none"; + + /* node can be null when parsing is incomplete */ + if (parse->node != NULL) { + id = parse->node->id; + typename = ec_node_type(parse->node)->name; + } + + fprintf(out, "%*s" "type=%s id=%s vec=", + (int)indent * 4, "", typename, id); + vec = ec_parse_strvec(parse); + ec_strvec_dump(out, vec); + + TAILQ_FOREACH(child, &parse->children, next) + __ec_parse_dump(out, child, indent + 1); +} + +void ec_parse_dump(FILE *out, const struct ec_parse *parse) +{ + fprintf(out, "------------------- parse dump:\n"); + + if (parse == NULL) { + fprintf(out, "parse is NULL\n"); + return; + } + + /* only exist if it does not match (strvec == NULL) and if it + * does not have children: an incomplete parse, like those + * generated by complete() don't match but have children that + * may match. */ + if (!ec_parse_matches(parse) && TAILQ_EMPTY(&parse->children)) { + fprintf(out, "no match\n"); + return; + } + + __ec_parse_dump(out, parse, 0); +} + +void ec_parse_link_child(struct ec_parse *parse, + struct ec_parse *child) +{ + TAILQ_INSERT_TAIL(&parse->children, child, next); + child->parent = parse; +} + +void ec_parse_unlink_child(struct ec_parse *parse, + struct ec_parse *child) +{ + TAILQ_REMOVE(&parse->children, child, next); + child->parent = NULL; +} + +struct ec_parse * +ec_parse_get_first_child(const struct ec_parse *parse) +{ + return TAILQ_FIRST(&parse->children); +} + +struct ec_parse * +ec_parse_get_last_child(const struct ec_parse *parse) +{ + return TAILQ_LAST(&parse->children, ec_parse_list); +} + +struct ec_parse *ec_parse_get_next(const struct ec_parse *parse) +{ + return TAILQ_NEXT(parse, next); +} + +bool ec_parse_has_child(const struct ec_parse *parse) +{ + return !TAILQ_EMPTY(&parse->children); +} + +const struct ec_node *ec_parse_get_node(const struct ec_parse *parse) +{ + return parse->node; +} + +void ec_parse_del_last_child(struct ec_parse *parse) +{ + struct ec_parse *child; + + child = ec_parse_get_last_child(parse); + ec_parse_unlink_child(parse, child); + ec_parse_free(child); +} + +struct ec_parse *__ec_parse_get_root(struct ec_parse *parse) +{ + if (parse == NULL) + return NULL; + + while (parse->parent != NULL) + parse = parse->parent; + + return parse; +} + +struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse) +{ + if (parse == NULL) + return NULL; + + return parse->parent; +} + +struct ec_parse *ec_parse_iter_next(struct ec_parse *parse) +{ + struct ec_parse *child, *parent, *next; + + child = TAILQ_FIRST(&parse->children); + if (child != NULL) + return child; + parent = parse->parent; + while (parent != NULL) { + next = TAILQ_NEXT(parse, next); + if (next != NULL) + return next; + parse = parent; + parent = parse->parent; + } + return NULL; +} + +struct ec_parse *ec_parse_find_first(struct ec_parse *parse, + const char *id) +{ + struct ec_parse *iter; + + if (parse == NULL) + return NULL; + + for (iter = parse; iter != NULL; iter = ec_parse_iter_next(iter)) { + if (iter->node != NULL && + iter->node->id != NULL && + !strcmp(iter->node->id, id)) + return iter; + } + + return NULL; +} + +struct ec_keyval * +ec_parse_get_attrs(struct ec_parse *parse) +{ + if (parse == NULL) + return NULL; + + return parse->attrs; +} + +const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse) +{ + if (parse == NULL || parse->strvec == NULL) + return NULL; + + return parse->strvec; +} + +/* number of strings in the parse vector */ +size_t ec_parse_len(const struct ec_parse *parse) +{ + if (parse == NULL || parse->strvec == NULL) + return 0; + + return ec_strvec_len(parse->strvec); +} + +size_t ec_parse_matches(const struct ec_parse *parse) +{ + if (parse == NULL) + return 0; + + if (parse->strvec == NULL) + return 0; + + return 1; +} + +/* LCOV_EXCL_START */ +static int ec_parse_testcase(void) +{ + struct ec_node *node = NULL; + struct ec_parse *p = NULL, *p2 = NULL; + const struct ec_parse *pc; + FILE *f = NULL; + char *buf = NULL; + size_t buflen = 0; + int testres = 0; + int ret; + + node = ec_node_sh_lex(EC_NO_ID, + EC_NODE_SEQ(EC_NO_ID, + ec_node_str("id_x", "x"), + ec_node_str("id_y", "y"))); + if (node == NULL) + goto fail; + + p = ec_node_parse(node, "xcdscds"); + testres |= EC_TEST_CHECK( + p != NULL && !ec_parse_matches(p), + "parse should not match\n"); + + f = open_memstream(&buf, &buflen); + if (f == NULL) + goto fail; + ec_parse_dump(f, p); + fclose(f); + f = NULL; + + testres |= EC_TEST_CHECK( + strstr(buf, "no match"), "bad dump\n"); + free(buf); + buf = NULL; + ec_parse_free(p); + + p = ec_node_parse(node, "x y"); + testres |= EC_TEST_CHECK( + p != NULL && ec_parse_matches(p), + "parse should match\n"); + testres |= EC_TEST_CHECK( + ec_parse_len(p) == 1, "bad parse len\n"); + + ret = ec_keyval_set(ec_parse_get_attrs(p), "key", "val", NULL); + testres |= EC_TEST_CHECK(ret == 0, + "cannot set parse attribute\n"); + + p2 = ec_parse_dup(p); + testres |= EC_TEST_CHECK( + p2 != NULL && ec_parse_matches(p2), + "parse should match\n"); + ec_parse_free(p2); + p2 = NULL; + + pc = ec_parse_find_first(p, "id_x"); + testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_x"); + testres |= EC_TEST_CHECK(pc != NULL && + ec_parse_get_parent(pc) != NULL && + ec_parse_get_parent(ec_parse_get_parent(pc)) == p, + "invalid parent\n"); + + pc = ec_parse_find_first(p, "id_y"); + testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_y"); + pc = ec_parse_find_first(p, "id_dezdezdez"); + testres |= EC_TEST_CHECK(pc == NULL, "should not find bad id"); + + + f = open_memstream(&buf, &buflen); + if (f == NULL) + goto fail; + ec_parse_dump(f, p); + fclose(f); + f = NULL; + + testres |= EC_TEST_CHECK( + strstr(buf, "type=sh_lex id=no-id") && + strstr(buf, "type=seq id=no-id") && + strstr(buf, "type=str id=id_x") && + strstr(buf, "type=str id=id_x"), + "bad dump\n"); + free(buf); + buf = NULL; + + ec_parse_free(p); + ec_node_free(node); + return testres; + +fail: + ec_parse_free(p2); + ec_parse_free(p); + ec_node_free(node); + if (f != NULL) + fclose(f); + free(buf); + + return -1; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_parse_test = { + .name = "parse", + .test = ec_parse_testcase, +}; + +EC_TEST_REGISTER(ec_parse_test); diff --git a/libecoli/ecoli_parse.h b/libecoli/ecoli_parse.h new file mode 100644 index 0000000..79e644f --- /dev/null +++ b/libecoli/ecoli_parse.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 +#include +#include +#include + +struct ec_node; +struct ec_parse; + +/** + * Create an empty parse tree. + * + * @return + * The empty parse tree. + */ +struct ec_parse *ec_parse(const struct ec_node *node); + +/** + * + * + * + */ +void ec_parse_free(struct ec_parse *parse); + +/** + * + * + * + */ +void ec_parse_free_children(struct ec_parse *parse); + +/** + * + * + * + */ +struct ec_parse *ec_parse_dup(const struct ec_parse *parse); + +/** + * + * + * + */ +const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse); + +/* a NULL return value is an error, with errno set + ENOTSUP: no ->parse() operation +*/ +/** + * + * + * + */ +struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str); + +/** + * + * + * + */ +struct ec_parse *ec_node_parse_strvec(const struct ec_node *node, + const struct ec_strvec *strvec); + +/** + * + * + * + */ +#define EC_PARSE_NOMATCH INT_MAX + +/* internal: used by nodes + * + * state is the current parse tree, which is built piece by piece while + * parsing the node tree: ec_node_parse_child() creates a new child in + * this state parse tree, and calls the parse() method for the child + * node, with state pointing to this new child. If it does not match, + * the child is removed in the state, else it is kept, with its + * possible descendants. + * + * return: + * the number of matched strings in strvec on success + * EC_PARSE_NOMATCH (positive) if it does not match + * -1 on error, and errno is set + */ +int ec_node_parse_child(const struct ec_node *node, + struct ec_parse *state, + const struct ec_strvec *strvec); + +/** + * + * + * + */ +void ec_parse_link_child(struct ec_parse *parse, + struct ec_parse *child); +/** + * + * + * + */ +void ec_parse_unlink_child(struct ec_parse *parse, + struct ec_parse *child); + +/* keep the const */ +#define ec_parse_get_root(parse) ({ \ + const struct ec_parse *p_ = parse; /* check type */ \ + struct ec_parse *parse_ = (struct ec_parse *)parse; \ + typeof(parse) res_; \ + (void)p_; \ + res_ = __ec_parse_get_root(parse_); \ + res_; \ +}) + +/** + * + * + * + */ +struct ec_parse *__ec_parse_get_root(struct ec_parse *parse); + +/** + * + * + * + */ +struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse); + +/** + * Get the first child of a tree. + * + */ +struct ec_parse *ec_parse_get_first_child(const struct ec_parse *parse); + +/** + * + * + * + */ +struct ec_parse *ec_parse_get_last_child(const struct ec_parse *parse); + +/** + * + * + * + */ +struct ec_parse *ec_parse_get_next(const struct ec_parse *parse); + +/** + * + * + * + */ +#define EC_PARSE_FOREACH_CHILD(child, parse) \ + for (child = ec_parse_get_first_child(parse); \ + child != NULL; \ + child = ec_parse_get_next(child)) \ + +/** + * + * + * + */ +bool ec_parse_has_child(const struct ec_parse *parse); + +/** + * + * + * + */ +const struct ec_node *ec_parse_get_node(const struct ec_parse *parse); + +/** + * + * + * + */ +void ec_parse_del_last_child(struct ec_parse *parse); + +/** + * + * + * + */ +struct ec_keyval *ec_parse_get_attrs(struct ec_parse *parse); + +/** + * + * + * + */ +void ec_parse_dump(FILE *out, const struct ec_parse *parse); + +/** + * + * + * + */ +struct ec_parse *ec_parse_find_first(struct ec_parse *parse, + const char *id); + +/** + * Iterate among parse tree + * + * Use it with: + * for (iter = state; iter != NULL; iter = ec_parse_iter_next(iter)) + */ +struct ec_parse *ec_parse_iter_next(struct ec_parse *parse); + +/** + * + * + * + */ +size_t ec_parse_len(const struct ec_parse *parse); + +/** + * + * + * + */ +size_t ec_parse_matches(const struct ec_parse *parse); + +#endif diff --git a/libecoli/ecoli_string.c b/libecoli/ecoli_string.c new file mode 100644 index 0000000..7723818 --- /dev/null +++ b/libecoli/ecoli_string.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include + +/* count the number of identical chars at the beginning of 2 strings */ +size_t ec_strcmp_count(const char *s1, const char *s2) +{ + size_t i = 0; + + while (s1[i] && s2[i] && s1[i] == s2[i]) + i++; + + return i; +} + +int ec_str_startswith(const char *s, const char *beginning) +{ + size_t len; + + len = ec_strcmp_count(s, beginning); + if (beginning[len] == '\0') + return 1; + + return 0; +} + +int ec_vasprintf(char **buf, const char *fmt, va_list ap) +{ + char dummy; + int buflen, ret; + va_list aq; + + va_copy(aq, ap); + *buf = NULL; + ret = vsnprintf(&dummy, 1, fmt, aq); + va_end(aq); + if (ret < 0) + return ret; + + buflen = ret + 1; + *buf = ec_malloc(buflen); + if (*buf == NULL) + return -1; + + va_copy(aq, ap); + ret = vsnprintf(*buf, buflen, fmt, aq); + va_end(aq); + + ec_assert_print(ret < buflen, "invalid return value for vsnprintf"); + if (ret < 0) { + free(*buf); + *buf = NULL; + return -1; + } + + return ret; +} + +int ec_asprintf(char **buf, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = ec_vasprintf(buf, fmt, ap); + va_end(ap); + + return ret; +} diff --git a/libecoli/ecoli_string.h b/libecoli/ecoli_string.h new file mode 100644 index 0000000..add73a0 --- /dev/null +++ b/libecoli/ecoli_string.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_STRING_ +#define ECOLI_STRING_ + +#include + +/* count the number of identical chars at the beginning of 2 strings */ +size_t ec_strcmp_count(const char *s1, const char *s2); + +/* return 1 if 's' starts with 'beginning' */ +int ec_str_startswith(const char *s, const char *beginning); + +/* like asprintf, but use libecoli allocator */ +int ec_asprintf(char **buf, const char *fmt, ...); + +/* like vasprintf, but use libecoli allocator */ +int ec_vasprintf(char **buf, const char *fmt, va_list ap); + +#endif diff --git a/libecoli/ecoli_strvec.c b/libecoli/ecoli_strvec.c new file mode 100644 index 0000000..c574b1b --- /dev/null +++ b/libecoli/ecoli_strvec.c @@ -0,0 +1,427 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#define _GNU_SOURCE /* qsort_r */ +#include +#include +#include +#include + +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(strvec); + +struct ec_strvec_elt { + unsigned int refcnt; + char *str; +}; + +struct ec_strvec { + size_t len; + struct ec_strvec_elt **vec; +}; + +struct ec_strvec *ec_strvec(void) +{ + struct ec_strvec *strvec; + + strvec = ec_calloc(1, sizeof(*strvec)); + if (strvec == NULL) + return NULL; + + return strvec; +} + +int ec_strvec_add(struct ec_strvec *strvec, const char *s) +{ + struct ec_strvec_elt *elt, **new_vec; + + new_vec = ec_realloc(strvec->vec, + sizeof(*strvec->vec) * (strvec->len + 1)); + if (new_vec == NULL) + return -1; + + strvec->vec = new_vec; + + elt = ec_malloc(sizeof(*elt)); + if (elt == NULL) + return -1; + + elt->str = ec_strdup(s); + if (elt->str == NULL) { + ec_free(elt); + return -1; + } + elt->refcnt = 1; + + new_vec[strvec->len] = elt; + strvec->len++; + return 0; +} + +struct ec_strvec *ec_strvec_from_array(const char * const *strarr, + size_t n) +{ + struct ec_strvec *strvec = NULL; + size_t i; + + strvec = ec_strvec(); + if (strvec == NULL) + goto fail; + + for (i = 0; i < n; i++) { + if (ec_strvec_add(strvec, strarr[i]) < 0) + goto fail; + } + + return strvec; + +fail: + ec_strvec_free(strvec); + return NULL; +} + +int ec_strvec_del_last(struct ec_strvec *strvec) +{ + struct ec_strvec_elt *elt; + + if (strvec->len == 0) { + errno = EINVAL; + return -1; + } + + elt = strvec->vec[strvec->len - 1]; + elt->refcnt--; + if (elt->refcnt == 0) { + ec_free(elt->str); + ec_free(elt); + } + strvec->len--; + return 0; +} + +struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t off, + size_t len) +{ + struct ec_strvec *copy = NULL; + size_t i, veclen; + + veclen = ec_strvec_len(strvec); + if (off + len > veclen) + return NULL; + + copy = ec_strvec(); + if (copy == NULL) + goto fail; + + if (len == 0) + return copy; + + copy->vec = ec_calloc(len, sizeof(*copy->vec)); + if (copy->vec == NULL) + goto fail; + + for (i = 0; i < len; i++) { + copy->vec[i] = strvec->vec[i + off]; + copy->vec[i]->refcnt++; + } + copy->len = len; + + return copy; + +fail: + ec_strvec_free(copy); + return NULL; +} + +struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec) +{ + return ec_strvec_ndup(strvec, 0, ec_strvec_len(strvec)); +} + +void ec_strvec_free(struct ec_strvec *strvec) +{ + struct ec_strvec_elt *elt; + size_t i; + + if (strvec == NULL) + return; + + for (i = 0; i < ec_strvec_len(strvec); i++) { + elt = strvec->vec[i]; + elt->refcnt--; + if (elt->refcnt == 0) { + ec_free(elt->str); + ec_free(elt); + } + } + + ec_free(strvec->vec); + ec_free(strvec); +} + +size_t ec_strvec_len(const struct ec_strvec *strvec) +{ + return strvec->len; +} + +const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx) +{ + if (strvec == NULL || idx >= strvec->len) + return NULL; + + return strvec->vec[idx]->str; +} + +int ec_strvec_cmp(const struct ec_strvec *strvec1, + const struct ec_strvec *strvec2) +{ + size_t i; + + if (ec_strvec_len(strvec1) != ec_strvec_len(strvec2)) + return -1; + + for (i = 0; i < ec_strvec_len(strvec1); i++) { + if (strcmp(ec_strvec_val(strvec1, i), + ec_strvec_val(strvec2, i))) + return -1; + } + + return 0; +} + +static int +cmp_vec_elt(const void *p1, const void *p2, void *arg) +{ + int (*str_cmp)(const char *s1, const char *s2) = arg; + const struct ec_strvec_elt * const *e1 = p1, * const *e2 = p2; + + return str_cmp((*e1)->str, (*e2)->str); +} + +void ec_strvec_sort(struct ec_strvec *strvec, + int (*str_cmp)(const char *s1, const char *s2)) +{ + if (str_cmp == NULL) + str_cmp = strcmp; + qsort_r(strvec->vec, ec_strvec_len(strvec), + sizeof(*strvec->vec), cmp_vec_elt, str_cmp); +} + +void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec) +{ + size_t i; + + if (strvec == NULL) { + fprintf(out, "none\n"); + return; + } + + fprintf(out, "strvec (len=%zu) [", strvec->len); + for (i = 0; i < ec_strvec_len(strvec); i++) { + if (i == 0) + fprintf(out, "%s", strvec->vec[i]->str); + else + fprintf(out, ", %s", strvec->vec[i]->str); + } + fprintf(out, "]\n"); + +} + +/* LCOV_EXCL_START */ +static int ec_strvec_testcase(void) +{ + struct ec_strvec *strvec = NULL; + struct ec_strvec *strvec2 = NULL; + FILE *f = NULL; + char *buf = NULL; + size_t buflen = 0; + int testres = 0; + + strvec = ec_strvec(); + if (strvec == NULL) { + EC_TEST_ERR("cannot create strvec\n"); + goto fail; + } + if (ec_strvec_len(strvec) != 0) { + EC_TEST_ERR("bad strvec len (0)\n"); + goto fail; + } + if (ec_strvec_add(strvec, "0") < 0) { + EC_TEST_ERR("cannot add (0) in strvec\n"); + goto fail; + } + if (ec_strvec_len(strvec) != 1) { + EC_TEST_ERR("bad strvec len (1)\n"); + goto fail; + } + if (ec_strvec_add(strvec, "1") < 0) { + EC_TEST_ERR("cannot add (1) in strvec\n"); + goto fail; + } + if (ec_strvec_len(strvec) != 2) { + EC_TEST_ERR("bad strvec len (2)\n"); + goto fail; + } + if (strcmp(ec_strvec_val(strvec, 0), "0")) { + EC_TEST_ERR("invalid element in strvec (0)\n"); + goto fail; + } + if (strcmp(ec_strvec_val(strvec, 1), "1")) { + EC_TEST_ERR("invalid element in strvec (1)\n"); + goto fail; + } + if (ec_strvec_val(strvec, 2) != NULL) { + EC_TEST_ERR("strvec val should be NULL\n"); + goto fail; + } + + strvec2 = ec_strvec_dup(strvec); + if (strvec2 == NULL) { + EC_TEST_ERR("cannot create strvec2\n"); + goto fail; + } + if (ec_strvec_len(strvec2) != 2) { + EC_TEST_ERR("bad strvec2 len (2)\n"); + goto fail; + } + if (strcmp(ec_strvec_val(strvec2, 0), "0")) { + EC_TEST_ERR("invalid element in strvec2 (0)\n"); + goto fail; + } + if (strcmp(ec_strvec_val(strvec2, 1), "1")) { + EC_TEST_ERR("invalid element in strvec2 (1)\n"); + goto fail; + } + if (ec_strvec_val(strvec2, 2) != NULL) { + EC_TEST_ERR("strvec2 val should be NULL\n"); + goto fail; + } + ec_strvec_free(strvec2); + + strvec2 = ec_strvec_ndup(strvec, 0, 0); + if (strvec2 == NULL) { + EC_TEST_ERR("cannot create strvec2\n"); + goto fail; + } + if (ec_strvec_len(strvec2) != 0) { + EC_TEST_ERR("bad strvec2 len (0)\n"); + goto fail; + } + if (ec_strvec_val(strvec2, 0) != NULL) { + EC_TEST_ERR("strvec2 val should be NULL\n"); + goto fail; + } + ec_strvec_free(strvec2); + + strvec2 = ec_strvec_ndup(strvec, 1, 1); + if (strvec2 == NULL) { + EC_TEST_ERR("cannot create strvec2\n"); + goto fail; + } + if (ec_strvec_len(strvec2) != 1) { + EC_TEST_ERR("bad strvec2 len (1)\n"); + goto fail; + } + if (strcmp(ec_strvec_val(strvec2, 0), "1")) { + EC_TEST_ERR("invalid element in strvec2 (1)\n"); + goto fail; + } + if (ec_strvec_val(strvec2, 1) != NULL) { + EC_TEST_ERR("strvec2 val should be NULL\n"); + goto fail; + } + ec_strvec_free(strvec2); + + strvec2 = ec_strvec_ndup(strvec, 3, 1); + if (strvec2 != NULL) { + EC_TEST_ERR("strvec2 should be NULL\n"); + goto fail; + } + ec_strvec_free(strvec2); + + strvec2 = EC_STRVEC("0", "1"); + if (strvec2 == NULL) { + EC_TEST_ERR("cannot create strvec from array\n"); + goto fail; + } + testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0, + "strvec and strvec2 should be equal\n"); + ec_strvec_free(strvec2); + + f = open_memstream(&buf, &buflen); + if (f == NULL) + goto fail; + ec_strvec_dump(f, strvec); + fclose(f); + f = NULL; + testres |= EC_TEST_CHECK( + strstr(buf, "strvec (len=2) [0, 1]"), "bad dump\n"); + free(buf); + buf = NULL; + + ec_strvec_del_last(strvec); + strvec2 = EC_STRVEC("0"); + if (strvec2 == NULL) { + EC_TEST_ERR("cannot create strvec from array\n"); + goto fail; + } + testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0, + "strvec and strvec2 should be equal\n"); + ec_strvec_free(strvec2); + strvec2 = NULL; + + f = open_memstream(&buf, &buflen); + if (f == NULL) + goto fail; + ec_strvec_dump(f, NULL); + fclose(f); + f = NULL; + testres |= EC_TEST_CHECK( + strstr(buf, "none"), "bad dump\n"); + free(buf); + buf = NULL; + + ec_strvec_free(strvec); + + strvec = EC_STRVEC("e", "a", "f", "d", "b", "c"); + if (strvec == NULL) { + EC_TEST_ERR("cannot create strvec from array\n"); + goto fail; + } + ec_strvec_sort(strvec, NULL); + strvec2 = EC_STRVEC("a", "b", "c", "d", "e", "f"); + if (strvec2 == NULL) { + EC_TEST_ERR("cannot create strvec from array\n"); + goto fail; + } + testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0, + "strvec and strvec2 should be equal\n"); + ec_strvec_free(strvec); + strvec = NULL; + ec_strvec_free(strvec2); + strvec2 = NULL; + + return testres; + +fail: + if (f != NULL) + fclose(f); + ec_strvec_free(strvec); + ec_strvec_free(strvec2); + free(buf); + + return -1; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_node_str_test = { + .name = "strvec", + .test = ec_strvec_testcase, +}; + +EC_TEST_REGISTER(ec_node_str_test); diff --git a/libecoli/ecoli_strvec.h b/libecoli/ecoli_strvec.h new file mode 100644 index 0000000..8e14973 --- /dev/null +++ b/libecoli/ecoli_strvec.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * 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 + +/** + * Allocate a new empty string vector. + * + * @return + * The new strvec object, or NULL on error (errno is set). + */ +struct ec_strvec *ec_strvec(void); + +#ifndef EC_COUNT_OF +#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \ + ((size_t)(!(sizeof(x) % sizeof(0[x]))))) +#endif + +/** + * Allocate a new string vector + * + * The string vector is initialized with the list of const strings + * passed as arguments. + * + * @return + * The new strvec object, or NULL on error (errno is set). + */ +#define EC_STRVEC(args...) ({ \ + const char *_arr[] = {args}; \ + ec_strvec_from_array(_arr, EC_COUNT_OF(_arr)); \ + }) +/** + * Allocate a new string vector + * + * The string vector is initialized with the array of const strings + * passed as arguments. + * + * @param strarr + * The array of const strings. + * @param n + * The number of strings in the array. + * @return + * The new strvec object, or NULL on error (errno is set). + */ +struct ec_strvec *ec_strvec_from_array(const char * const *strarr, + size_t n); + +/** + * Add a string in a vector. + * + * @param strvec + * The pointer to the string vector. + * @param s + * The string to be added at the end of the vector. + * @return + * 0 on success or -1 on error (errno is set). + */ +int ec_strvec_add(struct ec_strvec *strvec, const char *s); + +/** + * Delete the last entry in the string vector. + * + * @param strvec + * The pointer to the string vector. + * @param s + * The string to be added at the end of the vector. + * @return + * 0 on success or -1 on error (errno is set). + */ +int ec_strvec_del_last(struct ec_strvec *strvec); + +/** + * Duplicate a string vector. + * + * @param strvec + * The pointer to the string vector. + * @return + * The duplicated strvec object, or NULL on error (errno is set). + */ +struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec); + +/** + * Duplicate a part of a string vector. + * + * @param strvec + * The pointer to the string vector. + * @param off + * The index of the first string to duplicate. + * @param + * The number of strings to duplicate. + * @return + * The duplicated strvec object, or NULL on error (errno is set). + */ +struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, + size_t off, size_t len); + +/** + * Free a string vector. + * + * @param strvec + * The pointer to the string vector. + */ +void ec_strvec_free(struct ec_strvec *strvec); + +/** + * Get the length of a string vector. + * + * @param strvec + * The pointer to the string vector. + * @return + * The length of the vector. + */ +size_t ec_strvec_len(const struct ec_strvec *strvec); + +/** + * Get a string element from a vector. + * + * @param strvec + * The pointer to the string vector. + * @param idx + * The index of the string to get. + * @return + * The string stored at given index, or NULL on error (strvec is NULL + * or invalid index). + */ +const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx); + +/** + * Compare two string vectors + * + * @param strvec + * The pointer to the first string vector. + * @param strvec + * The pointer to the second string vector. + * @return + * 0 if the string vectors are equal. + */ +int ec_strvec_cmp(const struct ec_strvec *strvec1, + const struct ec_strvec *strvec2); + +/** + * Sort the string vector. + * + * @param strvec + * The pointer to the first string vector. + * @param str_cmp + * The sort function to use. If NULL, use strcmp. + */ +void ec_strvec_sort(struct ec_strvec *strvec, + int (*str_cmp)(const char *s1, const char *s2)); + +/** + * Dump a string vector. + * + * @param out + * The stream where the dump is sent. + * @param strvec + * The pointer to the string vector. + */ +void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec); + +#endif diff --git a/libecoli/ecoli_test.c b/libecoli/ecoli_test.c new file mode 100644 index 0000000..b090bd3 --- /dev/null +++ b/libecoli/ecoli_test.c @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct ec_test_list test_list = TAILQ_HEAD_INITIALIZER(test_list); + +EC_LOG_TYPE_REGISTER(test); + +static struct ec_test *ec_test_lookup(const char *name) +{ + struct ec_test *test; + + TAILQ_FOREACH(test, &test_list, next) { + if (!strcmp(name, test->name)) + return test; + } + + errno = EEXIST; + return NULL; +} + +int ec_test_register(struct ec_test *test) +{ + if (ec_test_lookup(test->name) != NULL) + return -1; + + TAILQ_INSERT_TAIL(&test_list, test, next); + + return 0; +} + +int ec_test_check_parse(struct ec_node *tk, int expected, ...) +{ + struct ec_parse *p; + struct ec_strvec *vec = NULL; + const char *s; + int ret = -1, match; + va_list ap; + + va_start(ap, expected); + + /* build a string vector */ + vec = ec_strvec(); + if (vec == NULL) + goto out; + + for (s = va_arg(ap, const char *); + s != EC_NODE_ENDLIST; + s = va_arg(ap, const char *)) { + if (s == NULL) + goto out; + + if (ec_strvec_add(vec, s) < 0) + goto out; + } + + p = ec_node_parse_strvec(tk, vec); + if (p == NULL) { + EC_LOG(EC_LOG_ERR, "parse is NULL\n"); + } + if (ec_parse_matches(p)) + match = ec_parse_len(p); + else + match = -1; + if (expected == match) { + ret = 0; + } else { + EC_LOG(EC_LOG_ERR, + "parse len (%d) does not match expected (%d)\n", + match, expected); + } + + ec_parse_free(p); + +out: + ec_strvec_free(vec); + va_end(ap); + return ret; +} + +int ec_test_check_complete(struct ec_node *tk, enum ec_comp_type type, ...) +{ + struct ec_comp *c = NULL; + struct ec_strvec *vec = NULL; + const char *s; + int ret = 0; + unsigned int count = 0; + va_list ap; + + va_start(ap, type); + + /* build a string vector */ + vec = ec_strvec(); + if (vec == NULL) + goto out; + + for (s = va_arg(ap, const char *); + s != EC_NODE_ENDLIST; + s = va_arg(ap, const char *)) { + if (s == NULL) + goto out; + + if (ec_strvec_add(vec, s) < 0) + goto out; + } + + c = ec_node_complete_strvec(tk, vec); + if (c == NULL) { + ret = -1; + goto out; + } + + /* for each expected completion, check it is there */ + for (s = va_arg(ap, const char *); + s != EC_NODE_ENDLIST; + s = va_arg(ap, const char *)) { + struct ec_comp_iter *iter; + const struct ec_comp_item *item; + + if (s == NULL) { + ret = -1; + goto out; + } + + count++; + + /* only check matching completions */ + iter = ec_comp_iter(c, type); + while ((item = ec_comp_iter_next(iter)) != NULL) { + const char *str = ec_comp_item_get_str(item); + if (str != NULL && strcmp(str, s) == 0) + break; + } + + if (item == NULL) { + EC_LOG(EC_LOG_ERR, + "completion <%s> not in list\n", s); + ret = -1; + } + ec_comp_iter_free(iter); + } + + /* check if we have more completions (or less) than expected */ + if (count != ec_comp_count(c, type)) { + EC_LOG(EC_LOG_ERR, + "nb_completion (%d) does not match (%d)\n", + count, ec_comp_count(c, type)); + ec_comp_dump(stdout, c); + ret = -1; + } + +out: + ec_strvec_free(vec); + ec_comp_free(c); + va_end(ap); + return ret; +} + +static int launch_test(const char *name) +{ + struct ec_test *test; + int ret = 0; + unsigned int count = 0; + + TAILQ_FOREACH(test, &test_list, next) { + if (name != NULL && strcmp(name, test->name)) + continue; + + EC_LOG(EC_LOG_INFO, "== starting test %-20s\n", + test->name); + + count++; + if (test->test() == 0) { + EC_LOG(EC_LOG_INFO, + "== test %-20s success\n", + test->name); + } else { + EC_LOG(EC_LOG_INFO, + "== test %-20s failed\n", + test->name); + ret = -1; + } + } + + if (name != NULL && count == 0) { + EC_LOG(EC_LOG_WARNING, + "== test %s not found\n", name); + ret = -1; + } + + return ret; +} + +int ec_test_all(void) +{ + return launch_test(NULL); +} + +int ec_test_one(const char *name) +{ + return launch_test(name); +} diff --git a/libecoli/ecoli_test.h b/libecoli/ecoli_test.h new file mode 100644 index 0000000..94cd0b9 --- /dev/null +++ b/libecoli/ecoli_test.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_TEST_ +#define ECOLI_TEST_ + +#include + +#include + +struct ec_node; +enum ec_comp_type; + +#define EC_TEST_REGISTER(t) \ + static void ec_test_init_##t(void); \ + static void __attribute__((constructor, used)) \ + ec_test_init_##t(void) \ + { \ + if (ec_test_register(&t) < 0) \ + fprintf(stderr, "cannot register test %s\n", \ + t.name); \ + } + +/** + * Type of test function. Return 0 on success, -1 on error. + */ +typedef int (ec_test_t)(void); + +TAILQ_HEAD(ec_test_list, ec_test); + +/** + * A structure describing a test case. + */ +struct ec_test { + TAILQ_ENTRY(ec_test) next; /**< Next in list. */ + const char *name; /**< Test name. */ + ec_test_t *test; /**< Test function. */ +}; + +/** + * Register a test case. + * + * @param test + * A pointer to a ec_test structure describing the test + * to be registered. + * @return + * 0 on success, -1 on error (errno is set). + */ +int ec_test_register(struct ec_test *test); + +int ec_test_all(void); +int ec_test_one(const char *name); + +/* expected == -1 means no match */ +int ec_test_check_parse(struct ec_node *node, int expected, ...); + +#define EC_TEST_ERR(fmt, ...) \ + EC_LOG(EC_LOG_ERR, "%s:%d: error: " fmt "\n", \ + __FILE__, __LINE__, ##__VA_ARGS__); \ + +#define EC_TEST_CHECK(cond, fmt, ...) ({ \ + int ret_ = 0; \ + if (!(cond)) { \ + EC_TEST_ERR("(" #cond ") is wrong. " fmt \ + ##__VA_ARGS__); \ + ret_ = -1; \ + } \ + ret_; \ +}) + +/* node, input, [expected1, expected2, ...] */ +#define EC_TEST_CHECK_PARSE(node, args...) ({ \ + int ret_ = ec_test_check_parse(node, args, EC_NODE_ENDLIST); \ + if (ret_) \ + EC_TEST_ERR("parse test failed"); \ + ret_; \ +}) + +int ec_test_check_complete(struct ec_node *node, + enum ec_comp_type type, ...); + +#define EC_TEST_CHECK_COMPLETE(node, args...) ({ \ + int ret_ = ec_test_check_complete(node, EC_COMP_FULL, args); \ + if (ret_) \ + EC_TEST_ERR("complete test failed"); \ + ret_; \ +}) + +#define EC_TEST_CHECK_COMPLETE_PARTIAL(node, args...) ({ \ + int ret_ = ec_test_check_complete(node, EC_COMP_PARTIAL, args); \ + if (ret_) \ + EC_TEST_ERR("complete test failed"); \ + ret_; \ +}) + +#endif diff --git a/libecoli/ecoli_vec.c b/libecoli/ecoli_vec.c new file mode 100644 index 0000000..fe8c572 --- /dev/null +++ b/libecoli/ecoli_vec.c @@ -0,0 +1,468 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(vec); + +struct ec_vec { + size_t len; + size_t size; + size_t elt_size; + ec_vec_elt_copy_t copy; + ec_vec_elt_free_t free; + void *vec; +}; + +static void *get_obj(const struct ec_vec *vec, size_t idx) +{ + assert(vec->elt_size != 0); + return (char *)vec->vec + (idx * vec->elt_size); +} + +struct ec_vec * +ec_vec(size_t elt_size, size_t size, ec_vec_elt_copy_t copy, + ec_vec_elt_free_t free) +{ + struct ec_vec *vec; + + if (elt_size == 0) { + errno = EINVAL; + return NULL; + } + + vec = ec_calloc(1, sizeof(*vec)); + if (vec == NULL) + return NULL; + + vec->elt_size = elt_size; + vec->copy = copy; + vec->free = free; + + if (size == 0) + return vec; + + vec->vec = ec_calloc(size, vec->elt_size); + if (vec->vec == NULL) { + ec_free(vec); + return NULL; + } + + return vec; +} + +int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr) +{ + void *new_vec; + + if (vec->len + 1 > vec->size) { + new_vec = ec_realloc(vec->vec, vec->elt_size * (vec->len + 1)); + if (new_vec == NULL) + return -1; + vec->size = vec->len + 1; + vec->vec = new_vec; + } + + memcpy(get_obj(vec, vec->len), ptr, vec->elt_size); + vec->len++; + + return 0; +} + +int ec_vec_add_ptr(struct ec_vec *vec, void *elt) +{ + EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); + + return ec_vec_add_by_ref(vec, &elt); +} + +int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt) +{ + EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); + + return ec_vec_add_by_ref(vec, &elt); +} + +int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt) +{ + EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); + + return ec_vec_add_by_ref(vec, &elt); +} + +int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt) +{ + EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); + + return ec_vec_add_by_ref(vec, &elt); +} + +int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt) +{ + EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL); + + return ec_vec_add_by_ref(vec, &elt); +} + +struct ec_vec *ec_vec_ndup(const struct ec_vec *vec, size_t off, + size_t len) +{ + struct ec_vec *copy = NULL; + size_t i, veclen; + + veclen = ec_vec_len(vec); + if (off + len > veclen) + return NULL; + + copy = ec_vec(vec->elt_size, len, vec->copy, vec->free); + if (copy == NULL) + goto fail; + + if (len == 0) + return copy; + + for (i = 0; i < len; i++) { + if (vec->copy) + vec->copy(get_obj(copy, i), get_obj(vec, i + off)); + else + memcpy(get_obj(copy, i), get_obj(vec, i + off), + vec->elt_size); + } + copy->len = len; + + return copy; + +fail: + ec_vec_free(copy); + return NULL; +} + +size_t ec_vec_len(const struct ec_vec *vec) +{ + if (vec == NULL) + return 0; + + return vec->len; +} + +struct ec_vec *ec_vec_dup(const struct ec_vec *vec) +{ + return ec_vec_ndup(vec, 0, ec_vec_len(vec)); +} + +void ec_vec_free(struct ec_vec *vec) +{ + size_t i; + + if (vec == NULL) + return; + + for (i = 0; i < ec_vec_len(vec); i++) { + if (vec->free) + vec->free(get_obj(vec, i)); + } + + ec_free(vec->vec); + ec_free(vec); +} + +int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx) +{ + if (vec == NULL || idx >= vec->len) { + errno = EINVAL; + return -1; + } + + memcpy(ptr, get_obj(vec, idx), vec->elt_size); + + return 0; +} + +static void str_free(void *elt) +{ + char **s = elt; + + ec_free(*s); +} + +#define GOTO_FAIL do { \ + EC_LOG(EC_LOG_ERR, "%s:%d: test failed\n", \ + __FILE__, __LINE__); \ + goto fail; \ + } while(0) + +/* LCOV_EXCL_START */ +static int ec_vec_testcase(void) +{ + struct ec_vec *vec = NULL; + struct ec_vec *vec2 = NULL; + uint8_t val8; + uint16_t val16; + uint32_t val32; + uint64_t val64; + void *valp; + char *vals; + + /* uint8_t vector */ + vec = ec_vec(sizeof(val8), 0, NULL, NULL); + if (vec == NULL) + GOTO_FAIL; + + if (ec_vec_add_u8(vec, 0) < 0) + GOTO_FAIL; + if (ec_vec_add_u8(vec, 1) < 0) + GOTO_FAIL; + if (ec_vec_add_u8(vec, 2) < 0) + GOTO_FAIL; + /* should fail */ + if (ec_vec_add_u16(vec, 3) == 0) + GOTO_FAIL; + if (ec_vec_add_u32(vec, 3) == 0) + GOTO_FAIL; + if (ec_vec_add_u64(vec, 3) == 0) + GOTO_FAIL; + if (ec_vec_add_ptr(vec, (void *)3) == 0) + GOTO_FAIL; + + if (ec_vec_get(&val8, vec, 0) < 0) + GOTO_FAIL; + if (val8 != 0) + GOTO_FAIL; + if (ec_vec_get(&val8, vec, 1) < 0) + GOTO_FAIL; + if (val8 != 1) + GOTO_FAIL; + if (ec_vec_get(&val8, vec, 2) < 0) + GOTO_FAIL; + if (val8 != 2) + GOTO_FAIL; + + /* duplicate the vector */ + vec2 = ec_vec_dup(vec); + if (vec2 == NULL) + GOTO_FAIL; + if (ec_vec_get(&val8, vec2, 0) < 0) + GOTO_FAIL; + if (val8 != 0) + GOTO_FAIL; + if (ec_vec_get(&val8, vec2, 1) < 0) + GOTO_FAIL; + if (val8 != 1) + GOTO_FAIL; + if (ec_vec_get(&val8, vec2, 2) < 0) + GOTO_FAIL; + if (val8 != 2) + GOTO_FAIL; + + ec_vec_free(vec2); + vec2 = NULL; + + /* dup at offset 1 */ + vec2 = ec_vec_ndup(vec, 1, 2); + if (vec2 == NULL) + GOTO_FAIL; + if (ec_vec_get(&val8, vec2, 0) < 0) + GOTO_FAIL; + if (val8 != 1) + GOTO_FAIL; + if (ec_vec_get(&val8, vec2, 1) < 0) + GOTO_FAIL; + if (val8 != 2) + GOTO_FAIL; + + ec_vec_free(vec2); + vec2 = NULL; + + /* len = 0, duplicate is empty */ + vec2 = ec_vec_ndup(vec, 2, 0); + if (vec2 == NULL) + GOTO_FAIL; + if (ec_vec_get(&val8, vec2, 0) == 0) + GOTO_FAIL; + + ec_vec_free(vec2); + vec2 = NULL; + + /* bad dup args */ + vec2 = ec_vec_ndup(vec, 10, 1); + if (vec2 != NULL) + GOTO_FAIL; + + ec_vec_free(vec); + vec = NULL; + + /* uint16_t vector */ + vec = ec_vec(sizeof(val16), 0, NULL, NULL); + if (vec == NULL) + GOTO_FAIL; + + if (ec_vec_add_u16(vec, 0) < 0) + GOTO_FAIL; + if (ec_vec_add_u16(vec, 1) < 0) + GOTO_FAIL; + if (ec_vec_add_u16(vec, 2) < 0) + GOTO_FAIL; + /* should fail */ + if (ec_vec_add_u8(vec, 3) == 0) + GOTO_FAIL; + + if (ec_vec_get(&val16, vec, 0) < 0) + GOTO_FAIL; + if (val16 != 0) + GOTO_FAIL; + if (ec_vec_get(&val16, vec, 1) < 0) + GOTO_FAIL; + if (val16 != 1) + GOTO_FAIL; + if (ec_vec_get(&val16, vec, 2) < 0) + GOTO_FAIL; + if (val16 != 2) + GOTO_FAIL; + + ec_vec_free(vec); + vec = NULL; + + /* uint32_t vector */ + vec = ec_vec(sizeof(val32), 0, NULL, NULL); + if (vec == NULL) + GOTO_FAIL; + + if (ec_vec_add_u32(vec, 0) < 0) + GOTO_FAIL; + if (ec_vec_add_u32(vec, 1) < 0) + GOTO_FAIL; + if (ec_vec_add_u32(vec, 2) < 0) + GOTO_FAIL; + + if (ec_vec_get(&val32, vec, 0) < 0) + GOTO_FAIL; + if (val32 != 0) + GOTO_FAIL; + if (ec_vec_get(&val32, vec, 1) < 0) + GOTO_FAIL; + if (val32 != 1) + GOTO_FAIL; + if (ec_vec_get(&val32, vec, 2) < 0) + GOTO_FAIL; + if (val32 != 2) + GOTO_FAIL; + + ec_vec_free(vec); + vec = NULL; + + /* uint64_t vector */ + vec = ec_vec(sizeof(val64), 0, NULL, NULL); + if (vec == NULL) + GOTO_FAIL; + + if (ec_vec_add_u64(vec, 0) < 0) + GOTO_FAIL; + if (ec_vec_add_u64(vec, 1) < 0) + GOTO_FAIL; + if (ec_vec_add_u64(vec, 2) < 0) + GOTO_FAIL; + + if (ec_vec_get(&val64, vec, 0) < 0) + GOTO_FAIL; + if (val64 != 0) + GOTO_FAIL; + if (ec_vec_get(&val64, vec, 1) < 0) + GOTO_FAIL; + if (val64 != 1) + GOTO_FAIL; + if (ec_vec_get(&val64, vec, 2) < 0) + GOTO_FAIL; + if (val64 != 2) + GOTO_FAIL; + + ec_vec_free(vec); + vec = NULL; + + /* ptr vector */ + vec = ec_vec(sizeof(valp), 0, NULL, NULL); + if (vec == NULL) + GOTO_FAIL; + + if (ec_vec_add_ptr(vec, (void *)0) < 0) + GOTO_FAIL; + if (ec_vec_add_ptr(vec, (void *)1) < 0) + GOTO_FAIL; + if (ec_vec_add_ptr(vec, (void *)2) < 0) + GOTO_FAIL; + + if (ec_vec_get(&valp, vec, 0) < 0) + GOTO_FAIL; + if (valp != (void *)0) + GOTO_FAIL; + if (ec_vec_get(&valp, vec, 1) < 0) + GOTO_FAIL; + if (valp != (void *)1) + GOTO_FAIL; + if (ec_vec_get(&valp, vec, 2) < 0) + GOTO_FAIL; + if (valp != (void *)2) + GOTO_FAIL; + + ec_vec_free(vec); + vec = NULL; + + /* string vector */ + vec = ec_vec(sizeof(valp), 0, NULL, str_free); + if (vec == NULL) + GOTO_FAIL; + + if (ec_vec_add_ptr(vec, ec_strdup("0")) < 0) + GOTO_FAIL; + if (ec_vec_add_ptr(vec, ec_strdup("1")) < 0) + GOTO_FAIL; + if (ec_vec_add_ptr(vec, ec_strdup("2")) < 0) + GOTO_FAIL; + + if (ec_vec_get(&vals, vec, 0) < 0) + GOTO_FAIL; + if (vals == NULL || strcmp(vals, "0")) + GOTO_FAIL; + if (ec_vec_get(&vals, vec, 1) < 0) + GOTO_FAIL; + if (vals == NULL || strcmp(vals, "1")) + GOTO_FAIL; + if (ec_vec_get(&vals, vec, 2) < 0) + GOTO_FAIL; + if (vals == NULL || strcmp(vals, "2")) + GOTO_FAIL; + + ec_vec_free(vec); + vec = NULL; + + /* invalid args */ + vec = ec_vec(0, 0, NULL, NULL); + if (vec != NULL) + GOTO_FAIL; + + return 0; + +fail: + ec_vec_free(vec); + ec_vec_free(vec2); + return -1; +} +/* LCOV_EXCL_STOP */ + +static struct ec_test ec_vec_test = { + .name = "vec", + .test = ec_vec_testcase, +}; + +EC_TEST_REGISTER(ec_vec_test); diff --git a/libecoli/ecoli_vec.h b/libecoli/ecoli_vec.h new file mode 100644 index 0000000..5fdaa99 --- /dev/null +++ b/libecoli/ecoli_vec.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +/** + * Vectors of objects. + * + * The ec_vec API provide helpers to manipulate vectors of objects + * of any kind. + */ + +#ifndef ECOLI_VEC_ +#define ECOLI_VEC_ + +#include +#include +#include + +/* if NULL, default does nothing */ +typedef void (*ec_vec_elt_free_t)(void *ptr); + +/* if NULL, default is: + * memcpy(dst, src, vec->elt_size) + */ +typedef void (*ec_vec_elt_copy_t)(void *dst, void *src); + +struct ec_vec *ec_vec(size_t elt_size, size_t size, + ec_vec_elt_copy_t copy, ec_vec_elt_free_t free); +int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr); + +int ec_vec_add_ptr(struct ec_vec *vec, void *elt); +int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt); +int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt); +int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt); +int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt); + +int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx); + +struct ec_vec *ec_vec_dup(const struct ec_vec *vec); +struct ec_vec *ec_vec_ndup(const struct ec_vec *vec, + size_t off, size_t len); +void ec_vec_free(struct ec_vec *vec); + +__attribute__((pure)) +size_t ec_vec_len(const struct ec_vec *vec); + +#endif diff --git a/test/test.c b/test/test.c new file mode 100644 index 0000000..1b2b7ef --- /dev/null +++ b/test/test.c @@ -0,0 +1,407 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* 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 \n" + " --"EC_OPT_LOG_LEVEL"=\n" + " Set log level (0 = no log, 7 = verbose).\n" + " -r \n" + " --"EC_OPT_RANDOM_ALLOC_FAIL"=\n" + " Cause malloc to fail randomly. This helps to debug\n" + " leaks or crashes in error cases. The probability is\n" + " between 0 and 100.\n" + " -s \n" + " --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 \n"); + + return -1; +} + +static int debug_log(int type, unsigned int level, void *opaque, + const char *str) +{ + (void)type; + (void)opaque; + + if (level > (unsigned int)log_level) + return 0; + + if (printf("%s", str) < 0) + return -1; + + return 0; +} + +int main(int argc, char **argv) +{ + int i, ret = 0, leaks; + + ret = parse_args(argc, argv); + if (ret < 0) + return 1; + + argc -= ret; + argv += ret; + + srandom(seed); + + /* register a new malloc to track memleaks */ + TAILQ_INIT(&debug_alloc_hdr_list); + if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) { + EC_LOG(EC_LOG_ERR, "cannot register new malloc\n"); + return 1; + } + + if (ec_init() < 0) { + fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno)); + return 1; + } + ec_log_fct_register(debug_log, NULL); + + ret = 0; + if (argc <= 1) { + ret = ec_test_all(); + } else { + for (i = 1; i < argc; i++) + ret |= ec_test_one(argv[i]); + } + + leaks = debug_alloc_dump_leaks(); + + if (alloc_fail_proba == 0 && ret != 0) { + printf("tests failed\n"); + return 1; + } else if (alloc_fail_proba != 0 && leaks != 0) { + printf("tests failed (memory leak)\n"); + return 1; + } + + printf("\ntests ok\n"); + + return 0; +} +/* LCOV_EXCL_STOP */ diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 0000000..83a7538 --- /dev/null +++ b/test/test.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2016, Olivier MATZ + +set -e + +SEED=100 +while [ ${SEED} -gt 0 ]; do + CMD="./build/test --random-alloc-fail=1 --seed=${SEED} $*" + ${CMD} --log-level=0 || ( + echo "=== test failed, replay seed=${SEED} with logs ===" && + ${CMD} --log-level=6 || + echo "=== test failed: ${CMD}" && + false + ) + + SEED=$((SEED-1)) && continue +done diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..4e5d94f --- /dev/null +++ b/todo.txt @@ -0,0 +1,421 @@ +tk_cmd +====== + +X evaluate expression tree in ec_tk_expr +X cmd token +- example +X tk_re + +cleanup / rework +================ + +X ec_completed_item_update() +X ec_completed_item_set_display_value() +X add_no_match +X add_partial_match +- check XXX in code +X properly manage quotes in shlex +X remove the _new() functions +X iterate children nodes without chaining them +- add a node vector type: will be used in several nodes (ex: or, seq, ...) +- check allocation model everywhere +- checkpatch? +- use linux style (update .emacs) +- better logs +- check return values (-1 or NULL) + use errno +- check missing static / const +- license: SPDX +- check all completion nodes +X split ecoli_tk.h +- size_t or unsigned int? +X rename: + X ec_tk -> ec_node + X ec_parsed_tk -> ec_parsed + X ec_completed_tk -> ec_completed + X tk, gen_tk, token, ... -> node + X tokens -> input_str / input_strvec ? +X save node path in completion to fix help string +- code coverage +- try to hide structures +- anything better than weakref? +- add ec_node_defaults.[ch] providing usual implementations of node methods +X use vec for strvec +- ELOOP in case of loop +- remove weakref? +- sh_lex to provide offsets in attributes +- accessors for all structs + +dependencies +============ + +X pass the current parsed state when parsing/completing +X new node "once" +- new node "condition" + +logs +==== + +X register log types + +yaml +==== + +X register nodes by name +- interface to add attributes: all nodes must be configurable through a + generic api + - attr string + - attr string list + - attr node + - attr node list + - attr int + +- yaml interface to create nodes +- example + +examples +======== + +- example which parses arguments (argc/argv) +- example that acts as bash completion (ip link ?) +- calculator example (var assignation, expression evaluation) +- example with libedit +- mini script language +- configuration file +- mini shell: cd, ls, cat, stat +- mini network console based on ip + +doc +=== + +- overview +- add api doc in .h +- generate automatic api doc +- architecture +- coding rules, process +- each node +- allocation model +- say that it stops at first match (no ambigous support) +- say that completion must be exhaustive + +build framework +=============== + +- .map files for API +- split libs, tests and examples +- add make help +- add make config +- -fvisibility= + +tests +===== + +- complete automatic tests with "make test" + +new nodes +========= + +- regexp +- node which always matches +- file + partial completion +- ether, ip, network +- fusion node: need to match several children, same for completion +- float +- not + +encoding +======== + +- support utf-8 and other encodings +- example +- documentation + +netconf example +=============== + +- demonstration example that parses yang file and generate cli + + + +----------------------- + +readline: + +[tab] list possible completions (matches/partial only) +[?] list what is expected, example: + +"command [foo] toto|titi|" + +help("command f") -> + foo (help of foo) + toto (help of toto) + titi (help of titi) + (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 +#include + + +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 malloc_seq >= + +alternative + +watch malloc_seq +condition malloc_seq == +run +c + + +--------------- + +