From: Olivier Matz Date: Mon, 11 Feb 2019 14:39:07 +0000 (+0100) Subject: add meson support X-Git-Url: http://git.droids-corp.org/?p=protos%2Flibecoli.git;a=commitdiff_plain;h=18d03456d96f7a086a2ccc82ce97fcf056848d90 add meson support --- diff --git a/Makefile b/Makefile deleted file mode 100644 index 8ccd7e2..0000000 --- a/Makefile +++ /dev/null @@ -1,87 +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 += -Ilibecoli -Ilibecoli_yaml -Ilibecoli_editline - -# 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 - -# libs -shlib-y-$(O)libecoli.so := $(addprefix libecoli/,$(srcs)) - -cflags-$(O)libecoli_yaml.so = -Ilibecoli_yaml -shlib-y-$(O)libecoli_yaml.so := libecoli_yaml/ecoli_yaml.c - -cflags-$(O)libecoli_editline.so = -Ilibecoli_editline -shlib-y-$(O)libecoli_editline.so := libecoli_editline/ecoli_editline.c - -# tests -ldflags-$(O)test = -rdynamic -exe-y-$(O)test = $(addprefix libecoli/,$(srcs)) test/test.c - -# examples -ldflags-$(O)readline = -lreadline -ltermcap -exe-y-$(O)readline = $(addprefix libecoli/,$(srcs)) \ - examples/readline/main.c - -ldflags-$(O)parse-yaml = -lyaml -ledit -exe-y-$(O)parse-yaml = $(addprefix libecoli/,$(srcs)) \ - libecoli_yaml/ecoli_yaml.c libecoli_editline/ecoli_editline.c\ - examples/yaml/parse-yaml.c - -include $(ECOLI)/mk/ecoli-post.mk - -all: _ecoli_all - -clean: _ecoli_clean - -.PHONY: clean all diff --git a/examples/meson.build b/examples/meson.build new file mode 100644 index 0000000..1435bfe --- /dev/null +++ b/examples/meson.build @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2018, Olivier MATZ + +subdir('readline') +subdir('parse-yaml') diff --git a/examples/parse-yaml/meson.build b/examples/parse-yaml/meson.build new file mode 100644 index 0000000..118fdc3 --- /dev/null +++ b/examples/parse-yaml/meson.build @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2018, Olivier MATZ + +inc = include_directories('../../include') + +parse_yaml_sources = [ + 'parse-yaml.c', +] + +ecoli_parse_yaml = executable( + 'ecoli-parse-yaml', + parse_yaml_sources, + include_directories : inc, + link_with : libecoli, + dependencies: [yaml_dep, edit_dep]) diff --git a/examples/parse-yaml/parse-yaml.c b/examples/parse-yaml/parse-yaml.c new file mode 100644 index 0000000..218fe78 --- /dev/null +++ b/examples/parse-yaml/parse-yaml.c @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static char *input_file; +static char *output_file; +static bool complete; + +static const char short_options[] = + "h" /* help */ + "i:" /* input-file */ + "o:" /* output-file */ + "c" /* complete */ + ; + +#define OPT_HELP "help" +#define OPT_INPUT_FILE "input-file" +#define OPT_OUTPUT_FILE "output-file" +#define OPT_COMPLETE "complete" + +static const struct option long_options[] = { + {OPT_HELP, 0, NULL, 'h'}, + {OPT_INPUT_FILE, 1, NULL, 'i'}, + {OPT_OUTPUT_FILE, 1, NULL, 'o'}, + {OPT_COMPLETE, 0, NULL, 'c'}, + {NULL, 0, NULL, 0} +}; + +static void usage(const char *prgname) +{ + fprintf(stderr, "%s -o -i \n" + " -h\n" + " --"OPT_HELP"\n" + " Show this help.\n" + " -i \n" + " --"OPT_INPUT_FILE"=\n" + " Set the yaml input file describing the grammar.\n" + " -o \n" + " --"OPT_OUTPUT_FILE"=\n" + " Set the output file.\n" + " -c\n" + " --"OPT_COMPLETE"\n" + " Output the completion list." + , prgname); +} + +static int parse_args(int argc, char **argv) +{ + int ret, opt; + + while ((opt = getopt_long(argc, argv, short_options, + long_options, NULL)) != EOF) { + + switch (opt) { + case 'h': /* help */ + usage(argv[0]); + exit(0); + + case 'i': /* input-file */ + input_file = strdup(optarg); + break; + + case 'o': /* output-file */ + output_file = strdup(optarg); + break; + + case 'c': /* complete */ + complete = 1; + break; + + default: + usage(argv[0]); + return -1; + } + + } + + if (input_file == NULL) { + fprintf(stderr, "No input file\n"); + usage(argv[0]); + return -1; + } + if (output_file == NULL) { + fprintf(stderr, "No output file\n"); + usage(argv[0]); + return -1; + } + + ret = optind - 1; + optind = 1; + + return ret; +} + +static int +__dump_as_shell(FILE *f, const struct ec_parse *parse, size_t *seq) +{ + const struct ec_node *node = ec_parse_get_node(parse); + struct ec_parse *child; + size_t cur_seq, i, len; + const char *s; + + (*seq)++; + cur_seq = *seq; + + // XXX protect strings + + + fprintf(f, "ec_node%zu_id='%s'\n", cur_seq, ec_node_id(node)); + fprintf(f, "ec_node%zu_type='%s'\n", cur_seq, + ec_node_type_name(ec_node_type(node))); + + len = ec_strvec_len(ec_parse_strvec(parse)); + fprintf(f, "ec_node%zu_strvec_len=%zu\n", cur_seq, len); + for (i = 0; i < len; i++) { + s = ec_strvec_val(ec_parse_strvec(parse), i); + fprintf(f, "ec_node%zu_str%zu='%s'\n", cur_seq, i, s); + } + + if (ec_parse_get_first_child(parse) != NULL) { + fprintf(f, "ec_node%zu_first_child='ec_node%zu'\n", + cur_seq, cur_seq + 1); + } + + EC_PARSE_FOREACH_CHILD(child, parse) { + fprintf(f, "ec_node%zu_parent='ec_node%zu'\n", + *seq + 1, cur_seq); + __dump_as_shell(f, child, seq); + } + + if (ec_parse_get_next(parse) != NULL) { + fprintf(f, "ec_node%zu_next='ec_node%zu'\n", + cur_seq, *seq + 1); + } + + return 0; +} + +static int +dump_as_shell(const struct ec_parse *parse) +{ + FILE *f; + size_t seq = 0; + int ret; + + f = fopen(output_file, "w"); + if (f == NULL) + return -1; + + ret = __dump_as_shell(f, parse, &seq); + + fclose(f); + + return ret; +} + +static int +interact(struct ec_node *node) +{ + struct ec_editline *editline = NULL; + struct ec_parse *parse = NULL; + struct ec_node *shlex = NULL; + char *line = NULL; + + shlex = ec_node_sh_lex(EC_NO_ID, ec_node_clone(node)); //XXX + if (shlex == NULL) { + fprintf(stderr, "Failed to add lexer node\n"); + goto fail; + } + + editline = ec_editline("ecoli", stdin, stdout, stderr, 0); + if (editline == NULL) { + fprintf(stderr, "Failed to initialize editline\n"); + goto fail; + } + + parse = ec_editline_parse(editline, shlex); + if (parse == NULL) + goto fail; + + if (!ec_parse_matches(parse)) + goto fail; + + //ec_parse_dump(stdout, parse); + + if (dump_as_shell(parse) < 0) { + fprintf(stderr, "Failed to dump the parsed result\n"); + goto fail; + } + + ec_parse_free(parse); + ec_editline_free(editline); + ec_node_free(shlex); + return 0; + +fail: + ec_parse_free(parse); + ec_editline_free(editline); + free(line); + ec_node_free(shlex); + return -1; +} + +static int +complete_words(const struct ec_node *node, int argc, char *argv[]) +{ + struct ec_comp *comp = NULL; + struct ec_strvec *strvec = NULL; + struct ec_comp_iter *iter = NULL; + struct ec_comp_item *item = NULL; + size_t count; + + if (argc <= 1) + goto fail; + strvec = ec_strvec_from_array((const char * const *)&argv[1], + argc - 1); + if (strvec == NULL) + goto fail; + + comp = ec_node_complete_strvec(node, strvec); + if (comp == NULL) + goto fail; + + count = ec_comp_count(comp, EC_COMP_UNKNOWN | EC_COMP_FULL | + EC_COMP_PARTIAL); + + iter = ec_comp_iter(comp, + EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL); + if (iter == NULL) + goto fail; + + while ((item = ec_comp_iter_next(iter)) != NULL) { + + /* only one match, display it fully */ + if (count == 1) { + printf("%s\n", ec_comp_item_get_str(item)); + break; + } + + /* else show the 'display' part only */ + printf("%s\n", ec_comp_item_get_display(item)); + } + + ec_comp_iter_free(iter); + ec_comp_free(comp); + ec_strvec_free(strvec); + return 0; + +fail: + ec_comp_free(comp); + ec_strvec_free(strvec); + return -1; +} + +int +main(int argc, char *argv[]) +{ + struct ec_node *node = NULL; + int ret; + + ret = parse_args(argc, argv); + if (ret < 0) + goto fail; + + argc -= ret; + argv += ret; + + if (ec_init() < 0) { + fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno)); + return 1; + } + + node = ec_yaml_import(input_file); + if (node == NULL) { + fprintf(stderr, "Failed to parse file\n"); + goto fail; + } + //ec_node_dump(stdout, node); + + if (complete) { + if (complete_words(node, argc, argv) < 0) + goto fail; + } else { + if (interact(node) < 0) + goto fail; + } + + ec_node_free(node); + + return 0; + +fail: + ec_node_free(node); + return 1; +} diff --git a/examples/parse-yaml/test.yaml b/examples/parse-yaml/test.yaml new file mode 100644 index 0000000..5642766 --- /dev/null +++ b/examples/parse-yaml/test.yaml @@ -0,0 +1,32 @@ +type: or +children: +- type: seq + id: hello + 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 +- type: seq + id: goodbye + help: Say good bye to someone + children: + - type: str + string: good + - type: str + string: bye + - type: or + id: name + help: Name of the person to greet + children: + - type: str + string: mary + - type: str + string: jessica diff --git a/examples/readline/meson.build b/examples/readline/meson.build new file mode 100644 index 0000000..513c85c --- /dev/null +++ b/examples/readline/meson.build @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2018, Olivier MATZ + +inc = include_directories('../../include') + +readline_sources = [ + 'main.c', +] + +c_compiler = meson.get_compiler('c') +readline = c_compiler.find_library('readline', required: false) +if readline.found() + ecoli_readline = executable( + 'ecoli-readline', + readline_sources, + include_directories : inc, + link_with : libecoli, + dependencies: readline) +endif diff --git a/examples/yaml/parse-yaml.c b/examples/yaml/parse-yaml.c deleted file mode 100644 index 218fe78..0000000 --- a/examples/yaml/parse-yaml.c +++ /dev/null @@ -1,310 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -static char *input_file; -static char *output_file; -static bool complete; - -static const char short_options[] = - "h" /* help */ - "i:" /* input-file */ - "o:" /* output-file */ - "c" /* complete */ - ; - -#define OPT_HELP "help" -#define OPT_INPUT_FILE "input-file" -#define OPT_OUTPUT_FILE "output-file" -#define OPT_COMPLETE "complete" - -static const struct option long_options[] = { - {OPT_HELP, 0, NULL, 'h'}, - {OPT_INPUT_FILE, 1, NULL, 'i'}, - {OPT_OUTPUT_FILE, 1, NULL, 'o'}, - {OPT_COMPLETE, 0, NULL, 'c'}, - {NULL, 0, NULL, 0} -}; - -static void usage(const char *prgname) -{ - fprintf(stderr, "%s -o -i \n" - " -h\n" - " --"OPT_HELP"\n" - " Show this help.\n" - " -i \n" - " --"OPT_INPUT_FILE"=\n" - " Set the yaml input file describing the grammar.\n" - " -o \n" - " --"OPT_OUTPUT_FILE"=\n" - " Set the output file.\n" - " -c\n" - " --"OPT_COMPLETE"\n" - " Output the completion list." - , prgname); -} - -static int parse_args(int argc, char **argv) -{ - int ret, opt; - - while ((opt = getopt_long(argc, argv, short_options, - long_options, NULL)) != EOF) { - - switch (opt) { - case 'h': /* help */ - usage(argv[0]); - exit(0); - - case 'i': /* input-file */ - input_file = strdup(optarg); - break; - - case 'o': /* output-file */ - output_file = strdup(optarg); - break; - - case 'c': /* complete */ - complete = 1; - break; - - default: - usage(argv[0]); - return -1; - } - - } - - if (input_file == NULL) { - fprintf(stderr, "No input file\n"); - usage(argv[0]); - return -1; - } - if (output_file == NULL) { - fprintf(stderr, "No output file\n"); - usage(argv[0]); - return -1; - } - - ret = optind - 1; - optind = 1; - - return ret; -} - -static int -__dump_as_shell(FILE *f, const struct ec_parse *parse, size_t *seq) -{ - const struct ec_node *node = ec_parse_get_node(parse); - struct ec_parse *child; - size_t cur_seq, i, len; - const char *s; - - (*seq)++; - cur_seq = *seq; - - // XXX protect strings - - - fprintf(f, "ec_node%zu_id='%s'\n", cur_seq, ec_node_id(node)); - fprintf(f, "ec_node%zu_type='%s'\n", cur_seq, - ec_node_type_name(ec_node_type(node))); - - len = ec_strvec_len(ec_parse_strvec(parse)); - fprintf(f, "ec_node%zu_strvec_len=%zu\n", cur_seq, len); - for (i = 0; i < len; i++) { - s = ec_strvec_val(ec_parse_strvec(parse), i); - fprintf(f, "ec_node%zu_str%zu='%s'\n", cur_seq, i, s); - } - - if (ec_parse_get_first_child(parse) != NULL) { - fprintf(f, "ec_node%zu_first_child='ec_node%zu'\n", - cur_seq, cur_seq + 1); - } - - EC_PARSE_FOREACH_CHILD(child, parse) { - fprintf(f, "ec_node%zu_parent='ec_node%zu'\n", - *seq + 1, cur_seq); - __dump_as_shell(f, child, seq); - } - - if (ec_parse_get_next(parse) != NULL) { - fprintf(f, "ec_node%zu_next='ec_node%zu'\n", - cur_seq, *seq + 1); - } - - return 0; -} - -static int -dump_as_shell(const struct ec_parse *parse) -{ - FILE *f; - size_t seq = 0; - int ret; - - f = fopen(output_file, "w"); - if (f == NULL) - return -1; - - ret = __dump_as_shell(f, parse, &seq); - - fclose(f); - - return ret; -} - -static int -interact(struct ec_node *node) -{ - struct ec_editline *editline = NULL; - struct ec_parse *parse = NULL; - struct ec_node *shlex = NULL; - char *line = NULL; - - shlex = ec_node_sh_lex(EC_NO_ID, ec_node_clone(node)); //XXX - if (shlex == NULL) { - fprintf(stderr, "Failed to add lexer node\n"); - goto fail; - } - - editline = ec_editline("ecoli", stdin, stdout, stderr, 0); - if (editline == NULL) { - fprintf(stderr, "Failed to initialize editline\n"); - goto fail; - } - - parse = ec_editline_parse(editline, shlex); - if (parse == NULL) - goto fail; - - if (!ec_parse_matches(parse)) - goto fail; - - //ec_parse_dump(stdout, parse); - - if (dump_as_shell(parse) < 0) { - fprintf(stderr, "Failed to dump the parsed result\n"); - goto fail; - } - - ec_parse_free(parse); - ec_editline_free(editline); - ec_node_free(shlex); - return 0; - -fail: - ec_parse_free(parse); - ec_editline_free(editline); - free(line); - ec_node_free(shlex); - return -1; -} - -static int -complete_words(const struct ec_node *node, int argc, char *argv[]) -{ - struct ec_comp *comp = NULL; - struct ec_strvec *strvec = NULL; - struct ec_comp_iter *iter = NULL; - struct ec_comp_item *item = NULL; - size_t count; - - if (argc <= 1) - goto fail; - strvec = ec_strvec_from_array((const char * const *)&argv[1], - argc - 1); - if (strvec == NULL) - goto fail; - - comp = ec_node_complete_strvec(node, strvec); - if (comp == NULL) - goto fail; - - count = ec_comp_count(comp, EC_COMP_UNKNOWN | EC_COMP_FULL | - EC_COMP_PARTIAL); - - iter = ec_comp_iter(comp, - EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL); - if (iter == NULL) - goto fail; - - while ((item = ec_comp_iter_next(iter)) != NULL) { - - /* only one match, display it fully */ - if (count == 1) { - printf("%s\n", ec_comp_item_get_str(item)); - break; - } - - /* else show the 'display' part only */ - printf("%s\n", ec_comp_item_get_display(item)); - } - - ec_comp_iter_free(iter); - ec_comp_free(comp); - ec_strvec_free(strvec); - return 0; - -fail: - ec_comp_free(comp); - ec_strvec_free(strvec); - return -1; -} - -int -main(int argc, char *argv[]) -{ - struct ec_node *node = NULL; - int ret; - - ret = parse_args(argc, argv); - if (ret < 0) - goto fail; - - argc -= ret; - argv += ret; - - if (ec_init() < 0) { - fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno)); - return 1; - } - - node = ec_yaml_import(input_file); - if (node == NULL) { - fprintf(stderr, "Failed to parse file\n"); - goto fail; - } - //ec_node_dump(stdout, node); - - if (complete) { - if (complete_words(node, argc, argv) < 0) - goto fail; - } else { - if (interact(node) < 0) - goto fail; - } - - ec_node_free(node); - - return 0; - -fail: - ec_node_free(node); - return 1; -} diff --git a/examples/yaml/test.yaml b/examples/yaml/test.yaml deleted file mode 100644 index 5642766..0000000 --- a/examples/yaml/test.yaml +++ /dev/null @@ -1,32 +0,0 @@ -type: or -children: -- type: seq - id: hello - 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 -- type: seq - id: goodbye - help: Say good bye to someone - children: - - type: str - string: good - - type: str - string: bye - - type: or - id: name - help: Name of the person to greet - children: - - type: str - string: mary - - type: str - string: jessica diff --git a/include/ecoli_assert.h b/include/ecoli_assert.h new file mode 100644 index 0000000..fcd2186 --- /dev/null +++ b/include/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/include/ecoli_complete.h b/include/ecoli_complete.h new file mode 100644 index 0000000..dee4123 --- /dev/null +++ b/include/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; + const struct ec_comp *comp; + struct ec_comp_group *cur_node; + struct ec_comp_item *cur_match; +}; + +/** + * + * + * + */ +struct ec_comp_iter * +ec_comp_iter(const 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/include/ecoli_config.h b/include/ecoli_config.h new file mode 100644 index 0000000..7aa427f --- /dev/null +++ b/include/ecoli_config.h @@ -0,0 +1,404 @@ +/* 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 */ + /* XXX flags: mandatory */ + /* XXX default */ + + /** 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); + +/** + * Count the number of elements in a list or dict. + * + * @param config + * The configuration that must be a list or a dict. + * @return + * The number of elements, or -1 on error (errno is set). + */ +ssize_t ec_config_count(const 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/include/ecoli_editline.h b/include/ecoli_editline.h new file mode 100644 index 0000000..58824d2 --- /dev/null +++ b/include/ecoli_editline.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +/** + * Helpers that can be used to associate an editline instance with + * an ecoli node tree. + * + * XXX support saved history + * XXX support multiline edition + * XXX set prompt + */ + +#ifndef ECOLI_EDITLINE_ +#define ECOLI_EDITLINE_ + +#include + +struct ec_editline; +struct ec_node; +struct ec_parse; +struct ec_comp; + +struct ec_editline_help { + char *desc; + char *help; +}; + +/** + * Default history size. + */ +#define EC_EDITLINE_HISTORY_SIZE 128 + +/** + * Ask the terminal to not send signals (STOP, SUSPEND, XXX). The + * ctrl-c, ctrl-z will be interpreted as standard characters. An + * action can be associated to these characters with: + * + * static int cb(EditLine *editline, int c) { + * { + * see editline documentation for details + * } + * + * if (el_set(el, EL_ADDFN, "ed-foobar", "Help string about foobar", cb)) + * handle_error; + * if (el_set(el, EL_BIND, "^C", "ed-break", NULL)) + * handle_error; + * + * The default behavior (without this flag) is to let the signal pass: ctrl-c + * will stop program and ctrl-z will suspend it. + */ +#define EC_EDITLINE_DISABLE_SIGNALS 0x01 + +/** + * Disable history. The default behavior creates an history with + * EC_EDITLINE_HISTORY_SIZE entries. To change this value, use + * ec_editline_set_history(). + */ +#define EC_EDITLINE_DISABLE_HISTORY 0x02 + +/** + * Disable completion. The default behavior is to complete when + * '?' or '' is hit. You can register your own callback with: + * + * if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer", callback)) + * handle_error; + * if (el_set(el, EL_BIND, "^I", "ed-complete", NULL)) + * handle_error; + * + * The default used callback is ec_editline_complete(). + */ +#define EC_EDITLINE_DISABLE_COMPLETION 0x04 + +typedef int (*ec_editline_cmpl_t)(struct ec_editline *editline, int c); + +/** + * Create an editline instance with default behavior. + * + * XXX Wrapper to editline's el_init() + * + * It + */ +struct ec_editline * +ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err, + unsigned int flags); + +/** + * Free an editline instance allocated with ec_editline(). + */ +void ec_editline_free(struct ec_editline *editline); + +/** + * Return the editline instance attached to the ec_editline object. + */ +EditLine *ec_editline_get_el(struct ec_editline *editline); + +// XXX public? +const struct ec_node *ec_editline_get_node(struct ec_editline *editline); +void ec_editline_set_node(struct ec_editline *editline, + const struct ec_node *node); + +//XXX get history, get_... + +/** + * Change the history size. + * + * The default behavior is to have an history whose size + * is EC_EDITLINE_HISTORY_SIZE. This can be changed with this + * function. + * + * @param editline + * The pointer to the ec_editline structure. + * @param hist_size + * The desired size of the history. + * @return + * 0 on success, or -1 on error (errno is set). + */ +int ec_editline_set_history(struct ec_editline *editline, + size_t hist_size); + +int +ec_editline_print_cols(struct ec_editline *editline, + char const * const *matches, size_t n); + +void ec_editline_free_completions(char **matches, size_t len); +ssize_t +ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out); +char * +ec_editline_append_chars(const struct ec_comp *cmpl); + +ssize_t +ec_editline_get_helps(const struct ec_editline *editline, const char *line, + const char *full_line, struct ec_editline_help **helps_out); +int +ec_editline_print_helps(struct ec_editline *editline, + const struct ec_editline_help *helps, size_t n); +void +ec_editline_free_helps(struct ec_editline_help *helps, size_t len); + +int +ec_editline_set_prompt(struct ec_editline *editline, const char *prompt); + + + + +/** + * Get a line. + * + * The returned line must be freed by the caller using ec_free(). + */ +char *ec_editline_gets(struct ec_editline *editline); + +/** + * Get a line (managing completion) and parse it with passed node + * XXX find a better name? + */ +struct ec_parse * +ec_editline_parse(struct ec_editline *editline, const struct ec_node *node); + +int +ec_editline_complete(EditLine *el, int c); + +#endif diff --git a/include/ecoli_init.h b/include/ecoli_init.h new file mode 100644 index 0000000..4e8bc1e --- /dev/null +++ b/include/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/include/ecoli_keyval.h b/include/ecoli_keyval.h new file mode 100644 index 0000000..a3e9400 --- /dev/null +++ b/include/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/include/ecoli_log.h b/include/ecoli_log.h new file mode 100644 index 0000000..2414dc0 --- /dev/null +++ b/include/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 registers 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/include/ecoli_malloc.h b/include/ecoli_malloc.h new file mode 100644 index 0000000..42cbf87 --- /dev/null +++ b/include/ecoli_malloc.h @@ -0,0 +1,255 @@ +/* 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_; \ + }) + +/** + * Ecoli realloc function. + * + * Use this function when the macro ec_realloc() cannot be used, + * for instance when it is passed as a callback pointer. + */ +void ec_realloc_func(void *ptr, size_t size); + +/** + * 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/include/ecoli_murmurhash.h b/include/ecoli_murmurhash.h new file mode 100644 index 0000000..6b76d34 --- /dev/null +++ b/include/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/include/ecoli_node.h b/include/ecoli_node.h new file mode 100644 index 0000000..45f3e74 --- /dev/null +++ b/include/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/include/ecoli_node_any.h b/include/ecoli_node_any.h new file mode 100644 index 0000000..ee638aa --- /dev/null +++ b/include/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/include/ecoli_node_cmd.h b/include/ecoli_node_cmd.h new file mode 100644 index 0000000..99afc01 --- /dev/null +++ b/include/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/include/ecoli_node_dynamic.h b/include/ecoli_node_dynamic.h new file mode 100644 index 0000000..4f2535e --- /dev/null +++ b/include/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/include/ecoli_node_empty.h b/include/ecoli_node_empty.h new file mode 100644 index 0000000..ed5e32e --- /dev/null +++ b/include/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/include/ecoli_node_expr.h b/include/ecoli_node_expr.h new file mode 100644 index 0000000..4f21d81 --- /dev/null +++ b/include/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/include/ecoli_node_file.h b/include/ecoli_node_file.h new file mode 100644 index 0000000..5760902 --- /dev/null +++ b/include/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/include/ecoli_node_helper.h b/include/ecoli_node_helper.h new file mode 100644 index 0000000..9dbf519 --- /dev/null +++ b/include/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/include/ecoli_node_int.h b/include/ecoli_node_int.h new file mode 100644 index 0000000..b64c60c --- /dev/null +++ b/include/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/include/ecoli_node_many.h b/include/ecoli_node_many.h new file mode 100644 index 0000000..0a50fd7 --- /dev/null +++ b/include/ecoli_node_many.h @@ -0,0 +1,18 @@ +/* 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); + +int +ec_node_many_set_params(struct ec_node *gen_node, struct ec_node *child, + unsigned int min, unsigned int max); + +#endif diff --git a/include/ecoli_node_none.h b/include/ecoli_node_none.h new file mode 100644 index 0000000..842f211 --- /dev/null +++ b/include/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/include/ecoli_node_once.h b/include/ecoli_node_once.h new file mode 100644 index 0000000..690a10c --- /dev/null +++ b/include/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 freed */ +int ec_node_once_set_child(struct ec_node *node, struct ec_node *child); + +#endif diff --git a/include/ecoli_node_option.h b/include/ecoli_node_option.h new file mode 100644 index 0000000..9f06d5f --- /dev/null +++ b/include/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_child(struct ec_node *gen_node, struct ec_node *child); + +#endif diff --git a/include/ecoli_node_or.h b/include/ecoli_node_or.h new file mode 100644 index 0000000..db115b2 --- /dev/null +++ b/include/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/include/ecoli_node_re.h b/include/ecoli_node_re.h new file mode 100644 index 0000000..bc3a317 --- /dev/null +++ b/include/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/include/ecoli_node_re_lex.h b/include/ecoli_node_re_lex.h new file mode 100644 index 0000000..94d426e --- /dev/null +++ b/include/ecoli_node_re_lex.h @@ -0,0 +1,15 @@ +/* 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, + const char *attr_name); + +#endif diff --git a/include/ecoli_node_seq.h b/include/ecoli_node_seq.h new file mode 100644 index 0000000..21c96b1 --- /dev/null +++ b/include/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/include/ecoli_node_sh_lex.h b/include/ecoli_node_sh_lex.h new file mode 100644 index 0000000..d45b998 --- /dev/null +++ b/include/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/include/ecoli_node_space.h b/include/ecoli_node_space.h new file mode 100644 index 0000000..0dd6202 --- /dev/null +++ b/include/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/include/ecoli_node_str.h b/include/ecoli_node_str.h new file mode 100644 index 0000000..8a8634f --- /dev/null +++ b/include/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/include/ecoli_node_subset.h b/include/ecoli_node_subset.h new file mode 100644 index 0000000..734b1ae --- /dev/null +++ b/include/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/include/ecoli_parse.h b/include/ecoli_parse.h new file mode 100644 index 0000000..a431ba2 --- /dev/null +++ b/include/ecoli_parse.h @@ -0,0 +1,239 @@ +/* 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 +#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); + +/** + * + * + * + */ +// _get_ XXX +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/include/ecoli_string.h b/include/ecoli_string.h new file mode 100644 index 0000000..a523b88 --- /dev/null +++ b/include/ecoli_string.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#ifndef ECOLI_STRING_ +#define ECOLI_STRING_ + +#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); + +/* 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); + +/* return true if string is only composed of spaces (' ', '\n', ...) */ +bool ec_str_is_space(const char *s); + +#endif diff --git a/include/ecoli_strvec.h b/include/ecoli_strvec.h new file mode 100644 index 0000000..cabe6b2 --- /dev/null +++ b/include/ecoli_strvec.h @@ -0,0 +1,224 @@ +/* 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); + +/** + * Set a string in the vector at specified index. + * + * @param strvec + * The pointer to the string vector. + * @param idx + * The index of the string to set. + * @param s + * The string to be set. + * @return + * 0 on success or -1 on error (errno is set). + */ +int ec_strvec_set(struct ec_strvec *strvec, size_t idx, const char *s); + +/** + * 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. + * + * Attributes are duplicated if any. + * + * @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. + * + * Attributes are duplicated if any. + * + * @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); + +/** + * Get the attributes of a vector element. + * + * @param strvec + * The pointer to the string vector. + * @param idx + * The index of the string to get. + * @return + * The read-only attributes (dictionnary) of the string at specified + * index, or NULL if there is no attribute. + */ +const struct ec_keyval *ec_strvec_get_attrs(const struct ec_strvec *strvec, + size_t idx); + +/** + * Set the attributes of a vector element. + * + * @param strvec + * The pointer to the string vector. + * @param idx + * The index of the string to get. + * @param attrs + * The attributes to be set. + * @return + * 0 on success, -1 on error (errno is set). On error, attrs + * are freed and must not be used by the caller. + */ +int ec_strvec_set_attrs(struct ec_strvec *strvec, size_t idx, + struct ec_keyval *attrs); + +/** + * 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. + * + * Attributes are not compared. + * + * @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/include/ecoli_test.h b/include/ecoli_test.h new file mode 100644 index 0000000..94cd0b9 --- /dev/null +++ b/include/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/include/ecoli_utils.h b/include/ecoli_utils.h new file mode 100644 index 0000000..5a14192 --- /dev/null +++ b/include/ecoli_utils.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#ifndef ECOLI_UTILS_ +#define ECOLI_UTILS_ + +/** + * Cast a variable into a type, ensuring its initial type first + */ +#define EC_CAST(x, old_type, new_type) ({ \ + old_type __x = (x); \ + (new_type)__x; \ + }) + +#endif diff --git a/include/ecoli_vec.h b/include/ecoli_vec.h new file mode 100644 index 0000000..5fdaa99 --- /dev/null +++ b/include/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/include/ecoli_yaml.h b/include/ecoli_yaml.h new file mode 100644 index 0000000..a63d837 --- /dev/null +++ b/include/ecoli_yaml.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +/** + * Interface to import/export ecoli data structures in YAML. + */ + +#ifndef ECOLI_NODE_YAML_ +#define ECOLI_NODE_YAML_ + +struct ec_node; + +/** + * Parse a YAML file and build an ec_node tree from it. + * + * @param filename + * The path to the file to be parsed. + * @return + * The ec_node tree on success, or NULL on error (errno is set). + * The returned node must be freed by the caller with ec_node_free(). + */ +struct ec_node *ec_yaml_import(const char *filename); + +#endif diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..ea33eb4 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2018, Olivier MATZ + +libecoli_headers = [ + 'ecoli_assert.h', + 'ecoli_complete.h', + 'ecoli_config.h', + 'ecoli_init.h', + 'ecoli_keyval.h', + 'ecoli_log.h', + 'ecoli_malloc.h', + 'ecoli_murmurhash.h', + 'ecoli_node_any.h', + 'ecoli_node_cmd.h', + 'ecoli_node_dynamic.h', + 'ecoli_node_empty.h', + 'ecoli_node_expr.h', + 'ecoli_node_file.h', + 'ecoli_node.h', + 'ecoli_node_helper.h', + 'ecoli_node_int.h', + 'ecoli_node_many.h', + 'ecoli_node_none.h', + 'ecoli_node_once.h', + 'ecoli_node_option.h', + 'ecoli_node_or.h', + 'ecoli_node_re.h', + 'ecoli_node_re_lex.h', + 'ecoli_node_seq.h', + 'ecoli_node_sh_lex.h', + 'ecoli_node_space.h', + 'ecoli_node_str.h', + 'ecoli_node_subset.h', + 'ecoli_parse.h', + 'ecoli_string.h', + 'ecoli_strvec.h', + 'ecoli_test.h', + 'ecoli_utils.h', + 'ecoli_vec.h', +] +install_headers(libecoli_headers) diff --git a/libecoli/ecoli_assert.c b/libecoli/ecoli_assert.c deleted file mode 100644 index a36d6f6..0000000 --- a/libecoli/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/libecoli/ecoli_assert.h b/libecoli/ecoli_assert.h deleted file mode 100644 index fcd2186..0000000 --- a/libecoli/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/libecoli/ecoli_complete.c b/libecoli/ecoli_complete.c deleted file mode 100644 index a9becdf..0000000 --- a/libecoli/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(const 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) -{ - const 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 deleted file mode 100644 index dee4123..0000000 --- a/libecoli/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; - const struct ec_comp *comp; - struct ec_comp_group *cur_node; - struct ec_comp_item *cur_match; -}; - -/** - * - * - * - */ -struct ec_comp_iter * -ec_comp_iter(const 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 deleted file mode 100644 index 66d9232..0000000 --- a/libecoli/ecoli_config.c +++ /dev/null @@ -1,1172 +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; -} - -ssize_t ec_config_count(const struct ec_config *config) -{ - const struct ec_config *child; - ssize_t n; - - switch (config->type) { - case EC_CONFIG_TYPE_LIST: - n = 0; - TAILQ_FOREACH(child, &config->list, next) - n++; - return n; - case EC_CONFIG_TYPE_DICT: - // XXX todo - default: - errno = EINVAL; - return -1; - } -} - -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 deleted file mode 100644 index 7aa427f..0000000 --- a/libecoli/ecoli_config.h +++ /dev/null @@ -1,404 +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 */ - /* XXX flags: mandatory */ - /* XXX default */ - - /** 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); - -/** - * Count the number of elements in a list or dict. - * - * @param config - * The configuration that must be a list or a dict. - * @return - * The number of elements, or -1 on error (errno is set). - */ -ssize_t ec_config_count(const 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 deleted file mode 100644 index fd5c0c3..0000000 --- a/libecoli/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/libecoli/ecoli_init.h b/libecoli/ecoli_init.h deleted file mode 100644 index 4e8bc1e..0000000 --- a/libecoli/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/libecoli/ecoli_keyval.c b/libecoli/ecoli_keyval.c deleted file mode 100644 index 12fe66b..0000000 --- a/libecoli/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/libecoli/ecoli_keyval.h b/libecoli/ecoli_keyval.h deleted file mode 100644 index a3e9400..0000000 --- a/libecoli/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/libecoli/ecoli_log.c b/libecoli/ecoli_log.c deleted file mode 100644 index aefba83..0000000 --- a/libecoli/ecoli_log.c +++ /dev/null @@ -1,229 +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; - - // XXX not that good to allocate in constructor - 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 deleted file mode 100644 index 2414dc0..0000000 --- a/libecoli/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 registers 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 deleted file mode 100644 index 5a022ae..0000000 --- a/libecoli/ecoli_malloc.c +++ /dev/null @@ -1,179 +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); -} - -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); -} - -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); -} - -void ec_realloc_func(void *ptr, size_t size) -{ - ec_realloc(ptr, size); -} - -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 deleted file mode 100644 index 42cbf87..0000000 --- a/libecoli/ecoli_malloc.h +++ /dev/null @@ -1,255 +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_; \ - }) - -/** - * Ecoli realloc function. - * - * Use this function when the macro ec_realloc() cannot be used, - * for instance when it is passed as a callback pointer. - */ -void ec_realloc_func(void *ptr, size_t size); - -/** - * 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 deleted file mode 100644 index 7aafece..0000000 --- a/libecoli/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/libecoli/ecoli_murmurhash.h b/libecoli/ecoli_murmurhash.h deleted file mode 100644 index 6b76d34..0000000 --- a/libecoli/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/libecoli/ecoli_node.c b/libecoli/ecoli_node.c deleted file mode 100644 index 9789102..0000000 --- a/libecoli/ecoli_node.c +++ /dev/null @@ -1,608 +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; - - // XXX check that id matches [_a-zA-Z][:-_0-9a-zA-Z]* - 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 deleted file mode 100644 index 45f3e74..0000000 --- a/libecoli/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/libecoli/ecoli_node_any.c b/libecoli/ecoli_node_any.c deleted file mode 100644 index 9166dbb..0000000 --- a/libecoli/ecoli_node_any.c +++ /dev/null @@ -1,141 +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 - -EC_LOG_TYPE_REGISTER(node_any); - -struct ec_node_any { - struct ec_node gen; - char *attr_name; -}; - -static int ec_node_any_parse(const struct ec_node *gen_node, - struct ec_parse *state, - const struct ec_strvec *strvec) -{ - struct ec_node_any *node = (struct ec_node_any *)gen_node; - const struct ec_keyval *attrs; - - (void)state; - - if (ec_strvec_len(strvec) == 0) - return EC_PARSE_NOMATCH; - if (node->attr_name != NULL) { - attrs = ec_strvec_get_attrs(strvec, 0); - if (attrs == NULL || !ec_keyval_has_key(attrs, node->attr_name)) - return EC_PARSE_NOMATCH; - } - - return 1; -} - -static void ec_node_any_free_priv(struct ec_node *gen_node) -{ - struct ec_node_any *node = (struct ec_node_any *)gen_node; - - ec_free(node->attr_name); -} - -static const struct ec_config_schema ec_node_any_schema[] = { - { - .key = "attr", - .desc = "The optional attribute name to attach.", - .type = EC_CONFIG_TYPE_STRING, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_any_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_any *node = (struct ec_node_any *)gen_node; - const struct ec_config *value = NULL; - char *s = NULL; - - value = ec_config_dict_get(config, "attr"); - if (value != NULL) { - s = ec_strdup(value->string); - if (s == NULL) - goto fail; - } - - ec_free(node->attr_name); - node->attr_name = s; - - return 0; - -fail: - ec_free(s); - return -1; -} - -static struct ec_node_type ec_node_any_type = { - .name = "any", - .schema = ec_node_any_schema, - .set_config = ec_node_any_set_config, - .parse = ec_node_any_parse, - .complete = ec_node_complete_unknown, - .size = sizeof(struct ec_node_any), - .free_priv = ec_node_any_free_priv, -}; - -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 deleted file mode 100644 index ee638aa..0000000 --- a/libecoli/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/libecoli/ecoli_node_cmd.c b/libecoli/ecoli_node_cmd.c deleted file mode 100644 index 3b56084..0000000 --- a/libecoli/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, NULL); - if (ret < 0) - goto fail; - ret = ec_node_re_lex_add(lex, "[*|,()]", 1, NULL); - if (ret < 0) - goto fail; - ret = ec_node_re_lex_add(lex, "\\[", 1, NULL); - if (ret < 0) - goto fail; - ret = ec_node_re_lex_add(lex, "\\]", 1, NULL); - if (ret < 0) - goto fail; - ret = ec_node_re_lex_add(lex, "[ ]+", 0, NULL); - if (ret < 0) - goto fail; - - return lex; - -fail: - ec_node_free(lex); - - 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 deleted file mode 100644 index 99afc01..0000000 --- a/libecoli/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/libecoli/ecoli_node_dynamic.c b/libecoli/ecoli_node_dynamic.c deleted file mode 100644 index 8a3edf3..0000000 --- a/libecoli/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/libecoli/ecoli_node_dynamic.h b/libecoli/ecoli_node_dynamic.h deleted file mode 100644 index 4f2535e..0000000 --- a/libecoli/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/libecoli/ecoli_node_empty.c b/libecoli/ecoli_node_empty.c deleted file mode 100644 index 6ce76e8..0000000 --- a/libecoli/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/libecoli/ecoli_node_empty.h b/libecoli/ecoli_node_empty.h deleted file mode 100644 index ed5e32e..0000000 --- a/libecoli/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/libecoli/ecoli_node_expr.c b/libecoli/ecoli_node_expr.c deleted file mode 100644 index 3b47d8c..0000000 --- a/libecoli/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/libecoli/ecoli_node_expr.h b/libecoli/ecoli_node_expr.h deleted file mode 100644 index 4f21d81..0000000 --- a/libecoli/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/libecoli/ecoli_node_expr_test.c b/libecoli/ecoli_node_expr_test.c deleted file mode 100644 index e3a0c79..0000000 --- a/libecoli/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, NULL); /* vars */ - testres |= ec_node_re_lex_add(lex_node, "[+*!^()]", 1, NULL); /* operators */ - testres |= ec_node_re_lex_add(lex_node, "[ ]+", 0, NULL); /* 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 deleted file mode 100644 index 001dcb6..0000000 --- a/libecoli/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/libecoli/ecoli_node_file.h b/libecoli/ecoli_node_file.h deleted file mode 100644 index 5760902..0000000 --- a/libecoli/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/libecoli/ecoli_node_helper.c b/libecoli/ecoli_node_helper.c deleted file mode 100644 index 94811f2..0000000 --- a/libecoli/ecoli_node_helper.c +++ /dev/null @@ -1,94 +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; - ssize_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 = ec_config_count(config); - if (n < 0) - return NULL; - - table = ec_calloc(n, sizeof(*table)); - if (table == NULL) - goto fail; - - n = 0; - TAILQ_FOREACH(child, &config->list, next) { - if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { - errno = EINVAL; - goto fail; - } - 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 deleted file mode 100644 index 9dbf519..0000000 --- a/libecoli/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/libecoli/ecoli_node_int.c b/libecoli/ecoli_node_int.c deleted file mode 100644 index 9b56e22..0000000 --- a/libecoli/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/libecoli/ecoli_node_int.h b/libecoli/ecoli_node_int.h deleted file mode 100644 index b64c60c..0000000 --- a/libecoli/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/libecoli/ecoli_node_many.c b/libecoli/ecoli_node_many.c deleted file mode 100644 index dfdd866..0000000 --- a/libecoli/ecoli_node_many.c +++ /dev/null @@ -1,416 +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_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 = 2; - return 0; -} - -static const struct ec_config_schema ec_node_many_schema[] = { - { - .key = "child", - .desc = "The child node.", - .type = EC_CONFIG_TYPE_NODE, - }, - { - .key = "min", - .desc = "The minimum number of matches (default = 0).", - .type = EC_CONFIG_TYPE_UINT64, - }, - { - .key = "max", - .desc = "The maximum number of matches. If 0, there is " - "no maximum (default = 0).", - .type = EC_CONFIG_TYPE_UINT64, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_many_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_many *node = (struct ec_node_many *)gen_node; - const struct ec_config *child, *min, *max; - - child = ec_config_dict_get(config, "child"); - if (child == NULL) - goto fail; - if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { - errno = EINVAL; - goto fail; - } - min = ec_config_dict_get(config, "min"); - if (min != NULL && (ec_config_get_type(min) != EC_CONFIG_TYPE_UINT64 || - min->u64 >= UINT_MAX)) { - errno = EINVAL; - goto fail; - } - max = ec_config_dict_get(config, "max"); - if (max != NULL && (ec_config_get_type(max) != EC_CONFIG_TYPE_UINT64 || - max->u64 >= UINT_MAX)) { - errno = EINVAL; - goto fail; - } - - if (node->child != NULL) - ec_node_free(node->child); - node->child = ec_node_clone(child->node); - if (min == NULL) - node->min = 0; - else - node->min = min->u64; - if (max == NULL) - node->max = 0; - else - node->max = max->u64; - - return 0; - -fail: - return -1; -} - -static struct ec_node_type ec_node_many_type = { - .name = "many", - .schema = ec_node_many_schema, - .set_config = ec_node_many_set_config, - .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); - -int -ec_node_many_set_params(struct ec_node *gen_node, struct ec_node *child, - unsigned int min, unsigned int max) -{ - const struct ec_config *cur_config = NULL; - struct ec_config *config = NULL; - int ret; - - if (ec_node_check_type(gen_node, &ec_node_many_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; - - if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { - child = NULL; /* freed */ - goto fail; - } - child = NULL; /* freed */ - - if (ec_config_dict_set(config, "min", ec_config_u64(min)) < 0) - goto fail; - if (ec_config_dict_set(config, "max", ec_config_u64(max)) < 0) - 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_many(const char *id, struct ec_node *child, - unsigned int min, unsigned int max) -{ - struct ec_node *gen_node = NULL; - - if (child == NULL) - return NULL; - - gen_node = ec_node_from_type(&ec_node_many_type, id); - if (gen_node == NULL) - goto fail; - - if (ec_node_many_set_params(gen_node, child, min, max) < 0) { - child = NULL; - goto fail; - } - child = NULL; - - return gen_node; - -fail: - ec_node_free(child); - return NULL; -} - -/* 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 deleted file mode 100644 index 0a50fd7..0000000 --- a/libecoli/ecoli_node_many.h +++ /dev/null @@ -1,18 +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); - -int -ec_node_many_set_params(struct ec_node *gen_node, 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 deleted file mode 100644 index dba9ebf..0000000 --- a/libecoli/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/libecoli/ecoli_node_none.h b/libecoli/ecoli_node_none.h deleted file mode 100644 index 842f211..0000000 --- a/libecoli/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/libecoli/ecoli_node_once.c b/libecoli/ecoli_node_once.c deleted file mode 100644 index 989c710..0000000 --- a/libecoli/ecoli_node_once.c +++ /dev/null @@ -1,282 +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_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 = 2; - return 0; -} - -static const struct ec_config_schema ec_node_once_schema[] = { - { - .key = "child", - .desc = "The child node.", - .type = EC_CONFIG_TYPE_NODE, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_once_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_once *node = (struct ec_node_once *)gen_node; - const struct ec_config *child; - - child = ec_config_dict_get(config, "child"); - if (child == NULL) - goto fail; - if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { - errno = EINVAL; - goto fail; - } - - if (node->child != NULL) - ec_node_free(node->child); - node->child = ec_node_clone(child->node); - - return 0; - -fail: - return -1; -} - -static struct ec_node_type ec_node_once_type = { - .name = "once", - .schema = ec_node_once_schema, - .set_config = ec_node_once_set_config, - .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_child(struct ec_node *gen_node, struct ec_node *child) -{ - const struct ec_config *cur_config = NULL; - struct ec_config *config = NULL; - int ret; - - if (ec_node_check_type(gen_node, &ec_node_once_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; - - if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { - child = NULL; /* freed */ - goto fail; - } - child = NULL; /* freed */ - - 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_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_child(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 deleted file mode 100644 index 690a10c..0000000 --- a/libecoli/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 freed */ -int ec_node_once_set_child(struct ec_node *node, struct ec_node *child); - -#endif diff --git a/libecoli/ecoli_node_option.c b/libecoli/ecoli_node_option.c deleted file mode 100644 index 4b26001..0000000 --- a/libecoli/ecoli_node_option.c +++ /dev/null @@ -1,239 +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_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 = 2; - return 0; -} - -static const struct ec_config_schema ec_node_option_schema[] = { - { - .key = "child", - .desc = "The child node.", - .type = EC_CONFIG_TYPE_NODE, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_option_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_option *node = (struct ec_node_option *)gen_node; - const struct ec_config *child; - - child = ec_config_dict_get(config, "child"); - if (child == NULL) - goto fail; - if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { - errno = EINVAL; - goto fail; - } - - if (node->child != NULL) - ec_node_free(node->child); - node->child = ec_node_clone(child->node); - - return 0; - -fail: - return -1; -} - -static struct ec_node_type ec_node_option_type = { - .name = "option", - .schema = ec_node_option_schema, - .set_config = ec_node_option_set_config, - .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_child(struct ec_node *gen_node, struct ec_node *child) -{ - const struct ec_config *cur_config = NULL; - struct ec_config *config = NULL; - int ret; - - if (ec_node_check_type(gen_node, &ec_node_option_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; - - if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { - child = NULL; /* freed */ - goto fail; - } - child = NULL; /* freed */ - - 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_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_child(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 deleted file mode 100644 index 9f06d5f..0000000 --- a/libecoli/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_child(struct ec_node *gen_node, struct ec_node *child); - -#endif diff --git a/libecoli/ecoli_node_or.c b/libecoli/ecoli_node_or.c deleted file mode 100644 index 2bf8fc7..0000000 --- a/libecoli/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/libecoli/ecoli_node_or.h b/libecoli/ecoli_node_or.h deleted file mode 100644 index db115b2..0000000 --- a/libecoli/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/libecoli/ecoli_node_re.c b/libecoli/ecoli_node_re.c deleted file mode 100644 index 69f352c..0000000 --- a/libecoli/ecoli_node_re.c +++ /dev/null @@ -1,203 +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 - -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 const struct ec_config_schema ec_node_re_schema[] = { - { - .key = "pattern", - .desc = "The pattern to match.", - .type = EC_CONFIG_TYPE_STRING, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_re_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_re *node = (struct ec_node_re *)gen_node; - const struct ec_config *value = NULL; - char *s = NULL; - regex_t re; - int ret; - - value = ec_config_dict_get(config, "pattern"); - if (value == NULL) { - errno = EINVAL; - goto fail; - } - - s = ec_strdup(value->string); - if (s == NULL) - goto fail; - - ret = regcomp(&re, s, 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 = s; - node->re = re; - - return 0; - -fail: - ec_free(s); - return -1; -} - -static struct ec_node_type ec_node_re_type = { - .name = "re", - .schema = ec_node_re_schema, - .set_config = ec_node_re_set_config, - .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_config *config = NULL; - int ret; - - EC_CHECK_ARG(str != NULL, -1, EINVAL); - - if (ec_node_check_type(gen_node, &ec_node_re_type) < 0) - goto fail; - - config = ec_config_dict(); - if (config == NULL) - goto fail; - - ret = ec_config_dict_set(config, "pattern", 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_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 deleted file mode 100644 index bc3a317..0000000 --- a/libecoli/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/libecoli/ecoli_node_re_lex.c b/libecoli/ecoli_node_re_lex.c deleted file mode 100644 index 4ebb9fd..0000000 --- a/libecoli/ecoli_node_re_lex.c +++ /dev/null @@ -1,564 +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 - -EC_LOG_TYPE_REGISTER(node_re_lex); - -struct regexp_pattern { - char *pattern; - char *attr_name; - 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; - struct ec_keyval *attrs = 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; - - if (table[i].attr_name != NULL) { - attrs = ec_keyval(); - if (attrs == NULL) - goto fail; - if (ec_keyval_set(attrs, table[i].attr_name, - NULL, NULL) < 0) - goto fail; - if (ec_strvec_set_attrs(strvec, - ec_strvec_len(strvec) - 1, - attrs) < 0) { - attrs = NULL; - goto fail; - } - attrs = NULL; - } - - 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 (node->child == NULL) { - errno = EINVAL; - goto fail; - } - - 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); - ec_free(node->table[i].attr_name); - 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 = 2; - return 0; -} - -static const struct ec_config_schema ec_node_re_lex_dict[] = { - { - .key = "pattern", - .desc = "The pattern to match.", - .type = EC_CONFIG_TYPE_STRING, - }, - { - .key = "keep", - .desc = "Whether to keep or drop the string matching " - "the regular expression.", - .type = EC_CONFIG_TYPE_BOOL, - }, - { - .key = "attr", - .desc = "The optional attribute name to attach.", - .type = EC_CONFIG_TYPE_STRING, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static const struct ec_config_schema ec_node_re_lex_elt[] = { - { - .desc = "A pattern element.", - .type = EC_CONFIG_TYPE_DICT, - .subschema = ec_node_re_lex_dict, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static const struct ec_config_schema ec_node_re_lex_schema[] = { - { - .key = "patterns", - .desc = "The list of patterns elements.", - .type = EC_CONFIG_TYPE_LIST, - .subschema = ec_node_re_lex_elt, - }, - { - .key = "child", - .desc = "The child node.", - .type = EC_CONFIG_TYPE_NODE, - }, - { - .type = EC_CONFIG_TYPE_NONE, - }, -}; - -static int ec_node_re_lex_set_config(struct ec_node *gen_node, - const struct ec_config *config) -{ - struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; - struct regexp_pattern *table = NULL; - const struct ec_config *patterns, *child, *elt, *pattern, *keep, *attr; - char *pattern_str = NULL, *attr_name = NULL; - ssize_t i, n = 0; - int ret; - - child = ec_config_dict_get(config, "child"); - if (child == NULL) - goto fail; - if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { - errno = EINVAL; - goto fail; - } - - patterns = ec_config_dict_get(config, "patterns"); - if (patterns != NULL) { - n = ec_config_count(patterns); - if (n < 0) - goto fail; - - table = ec_calloc(n, sizeof(*table)); - if (table == NULL) - goto fail; - - n = 0; - TAILQ_FOREACH(elt, &patterns->list, next) { - if (ec_config_get_type(elt) != EC_CONFIG_TYPE_DICT) { - errno = EINVAL; - goto fail; - } - pattern = ec_config_dict_get(elt, "pattern"); - if (pattern == NULL) { - errno = EINVAL; - goto fail; - } - if (ec_config_get_type(pattern) != EC_CONFIG_TYPE_STRING) { - errno = EINVAL; - goto fail; - } - keep = ec_config_dict_get(elt, "keep"); - if (keep == NULL) { - errno = EINVAL; - goto fail; - } - if (ec_config_get_type(keep) != EC_CONFIG_TYPE_BOOL) { - errno = EINVAL; - goto fail; - } - attr = ec_config_dict_get(elt, "attr"); - if (attr != NULL && ec_config_get_type(attr) != - EC_CONFIG_TYPE_STRING) { - errno = EINVAL; - goto fail; - } - pattern_str = ec_strdup(pattern->string); - if (pattern_str == NULL) - goto fail; - if (attr != NULL && attr->string != NULL) { - attr_name = ec_strdup(attr->string); - if (attr_name == NULL) - goto fail; - } - - ret = regcomp(&table[n].r, pattern_str, REG_EXTENDED); - if (ret != 0) { - EC_LOG(EC_LOG_ERR, - "Regular expression <%s> compilation failed: %d\n", - pattern_str, ret); - if (ret == REG_ESPACE) - errno = ENOMEM; - else - errno = EINVAL; - goto fail; - } - table[n].pattern = pattern_str; - table[n].keep = keep->boolean; - table[n].attr_name = attr_name; - pattern_str = NULL; - attr_name = NULL; - - n++; - } - } - - if (node->child != NULL) - ec_node_free(node->child); - node->child = ec_node_clone(child->node); - for (i = 0; i < (ssize_t)node->len; i++) { - ec_free(node->table[i].pattern); - regfree(&node->table[i].r); - } - ec_free(node->table); - node->table = table; - node->len = n; - - return 0; - -fail: - if (table != NULL) { - for (i = 0; i < n; i++) { - if (table[i].pattern != NULL) { - ec_free(table[i].pattern); - regfree(&table[i].r); - } - } - } - ec_free(table); - ec_free(pattern_str); - return -1; -} - -static struct ec_node_type ec_node_re_lex_type = { - .name = "re_lex", - .schema = ec_node_re_lex_schema, - .set_config = ec_node_re_lex_set_config, - .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, - const char *attr_name) -{ - const struct ec_config *cur_config = NULL; - struct ec_config *config = NULL, *patterns = NULL, *elt = NULL; - int ret; - - if (ec_node_check_type(gen_node, &ec_node_re_lex_type) < 0) - goto fail; - - elt = ec_config_dict(); - if (elt == NULL) - goto fail; - if (ec_config_dict_set(elt, "pattern", ec_config_string(pattern)) < 0) - goto fail; - if (ec_config_dict_set(elt, "keep", ec_config_bool(keep)) < 0) - goto fail; - if (attr_name != NULL) { - if (ec_config_dict_set(elt, "attr", - ec_config_string(attr_name)) < 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; - - patterns = ec_config_dict_get(config, "patterns"); - if (patterns == NULL) { - patterns = ec_config_list(); - if (patterns == NULL) - goto fail; - - if (ec_config_dict_set(config, "patterns", patterns) < 0) - goto fail; /* patterns list is freed on error */ - } - - if (ec_config_list_add(patterns, elt) < 0) { - elt = NULL; - goto fail; - } - elt = NULL; - - ret = ec_node_set_config(gen_node, config); - config = NULL; /* freed */ - if (ret < 0) - goto fail; - - return 0; - -fail: - ec_config_free(config); - ec_config_free(elt); - return -1; -} - -static int -ec_node_re_lex_set_child(struct ec_node *gen_node, struct ec_node *child) -{ - const struct ec_config *cur_config = NULL; - struct ec_config *config = NULL; - int ret; - - if (ec_node_check_type(gen_node, &ec_node_re_lex_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; - - if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { - child = NULL; /* freed */ - goto fail; - } - child = NULL; /* freed */ - - 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_re_lex(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_re_lex_type, id); - if (gen_node == NULL) - goto fail; - - if (ec_node_re_lex_set_child(gen_node, child) < 0) { - child = NULL; /* freed */ - goto fail; - } - - return gen_node; - -fail: - ec_node_free(gen_node); - ec_node_free(child); - return NULL; -} - -/* 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, NULL); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "[0-9]+", 1, NULL); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "=", 1, NULL); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "-", 1, NULL); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "\\+", 1, NULL); - testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); - ret = ec_node_re_lex_add(node, "[ ]+", 0, NULL); - 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 deleted file mode 100644 index 94d426e..0000000 --- a/libecoli/ecoli_node_re_lex.h +++ /dev/null @@ -1,15 +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, - const char *attr_name); - -#endif diff --git a/libecoli/ecoli_node_seq.c b/libecoli/ecoli_node_seq.c deleted file mode 100644 index ff0c5de..0000000 --- a/libecoli/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/libecoli/ecoli_node_seq.h b/libecoli/ecoli_node_seq.h deleted file mode 100644 index 21c96b1..0000000 --- a/libecoli/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/libecoli/ecoli_node_sh_lex.c b/libecoli/ecoli_node_sh_lex.c deleted file mode 100644 index e27f21b..0000000 --- a/libecoli/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/libecoli/ecoli_node_sh_lex.h b/libecoli/ecoli_node_sh_lex.h deleted file mode 100644 index d45b998..0000000 --- a/libecoli/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/libecoli/ecoli_node_space.c b/libecoli/ecoli_node_space.c deleted file mode 100644 index 761ed76..0000000 --- a/libecoli/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/libecoli/ecoli_node_space.h b/libecoli/ecoli_node_space.h deleted file mode 100644 index 0dd6202..0000000 --- a/libecoli/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/libecoli/ecoli_node_str.c b/libecoli/ecoli_node_str.c deleted file mode 100644 index d53ea39..0000000 --- a/libecoli/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/libecoli/ecoli_node_str.h b/libecoli/ecoli_node_str.h deleted file mode 100644 index 8a8634f..0000000 --- a/libecoli/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/libecoli/ecoli_node_subset.c b/libecoli/ecoli_node_subset.c deleted file mode 100644 index e3184ef..0000000 --- a/libecoli/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/libecoli/ecoli_node_subset.h b/libecoli/ecoli_node_subset.h deleted file mode 100644 index 734b1ae..0000000 --- a/libecoli/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/libecoli/ecoli_parse.c b/libecoli/ecoli_parse.c deleted file mode 100644 index 3d6be77..0000000 --- a/libecoli/ecoli_parse.c +++ /dev/null @@ -1,544 +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); -} - -// XXX what is returned if no match ?? -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) -{ - if (parse == NULL) - return NULL; - - 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 deleted file mode 100644 index a431ba2..0000000 --- a/libecoli/ecoli_parse.h +++ /dev/null @@ -1,239 +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 -#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); - -/** - * - * - * - */ -// _get_ XXX -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 deleted file mode 100644 index fd427b4..0000000 --- a/libecoli/ecoli_string.c +++ /dev/null @@ -1,89 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#include -#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; -} - -bool ec_str_is_space(const char *s) -{ - while (*s) { - if (!isspace(*s)) - return false; - s++; - } - return true; -} diff --git a/libecoli/ecoli_string.h b/libecoli/ecoli_string.h deleted file mode 100644 index a523b88..0000000 --- a/libecoli/ecoli_string.h +++ /dev/null @@ -1,26 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2016, Olivier MATZ - */ - -#ifndef ECOLI_STRING_ -#define ECOLI_STRING_ - -#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); - -/* 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); - -/* return true if string is only composed of spaces (' ', '\n', ...) */ -bool ec_str_is_space(const char *s); - -#endif diff --git a/libecoli/ecoli_strvec.c b/libecoli/ecoli_strvec.c deleted file mode 100644 index 98a952f..0000000 --- a/libecoli/ecoli_strvec.c +++ /dev/null @@ -1,536 +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 -#include - -EC_LOG_TYPE_REGISTER(strvec); - -struct ec_strvec_elt { - unsigned int refcnt; - char *str; - struct ec_keyval *attrs; -}; - -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; -} - -static struct ec_strvec_elt * -__ec_strvec_elt(const char *s) -{ - struct ec_strvec_elt *elt; - - elt = ec_calloc(1, sizeof(*elt)); - if (elt == NULL) - return NULL; - - elt->str = ec_strdup(s); - if (elt->str == NULL) { - ec_free(elt); - return NULL; - } - elt->refcnt = 1; - - return elt; -} - -static void -__ec_strvec_elt_free(struct ec_strvec_elt *elt) -{ - elt->refcnt--; - if (elt->refcnt == 0) { - ec_free(elt->str); - ec_keyval_free(elt->attrs); - ec_free(elt); - } -} - -int ec_strvec_set(struct ec_strvec *strvec, size_t idx, const char *s) -{ - struct ec_strvec_elt *elt; - - if (strvec == NULL || s == NULL || idx >= strvec->len) { - errno = EINVAL; - return -1; - } - - elt = __ec_strvec_elt(s); - if (elt == NULL) - return -1; - - __ec_strvec_elt_free(strvec->vec[idx]); - strvec->vec[idx] = elt; - - return 0; -} - -int ec_strvec_add(struct ec_strvec *strvec, const char *s) -{ - struct ec_strvec_elt *elt, **new_vec; - - if (strvec == NULL || s == NULL) { - errno = EINVAL; - return -1; - } - - new_vec = ec_realloc(strvec->vec, - sizeof(*strvec->vec) * (strvec->len + 1)); - if (new_vec == NULL) - return -1; - - strvec->vec = new_vec; - - elt = __ec_strvec_elt(s); - if (elt == NULL) - return -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) -{ - if (strvec->len == 0) { - errno = EINVAL; - return -1; - } - - __ec_strvec_elt_free(strvec->vec[strvec->len - 1]); - 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]; - __ec_strvec_elt_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; -} - -const struct ec_keyval *ec_strvec_get_attrs(const struct ec_strvec *strvec, - size_t idx) -{ - if (strvec == NULL || idx >= strvec->len) { - errno = EINVAL; - return NULL; - } - - return strvec->vec[idx]->attrs; -} - -int ec_strvec_set_attrs(struct ec_strvec *strvec, size_t idx, - struct ec_keyval *attrs) -{ - struct ec_strvec_elt *elt; - - if (strvec == NULL || idx >= strvec->len) { - errno = EINVAL; - goto fail; - } - - elt = strvec->vec[idx]; - if (elt->refcnt > 1) { - if (ec_strvec_set(strvec, idx, elt->str) < 0) - goto fail; - elt = strvec->vec[idx]; - } - - if (elt->attrs != NULL) - ec_keyval_free(elt->attrs); - - elt->attrs = attrs; - - return 0; - -fail: - ec_keyval_free(attrs); - return -1; -} - -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; - const struct ec_keyval *const_attrs = NULL; - struct ec_keyval *attrs = 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; - } - attrs = ec_keyval(); - if (attrs == NULL) { - EC_TEST_ERR("cannot create attrs\n"); - goto fail; - } - if (ec_keyval_set(attrs, "key", "value", NULL) < 0) { - EC_TEST_ERR("cannot set attr\n"); - goto fail; - } - if (ec_strvec_set_attrs(strvec, 1, attrs) < 0) { - attrs = NULL; - EC_TEST_ERR("cannot set attrs in strvec\n"); - goto fail; - } - attrs = NULL; - - ec_strvec_sort(strvec, NULL); - - /* attrs are now at index 0 after sorting */ - const_attrs = ec_strvec_get_attrs(strvec, 0); - if (const_attrs == NULL) { - EC_TEST_ERR("cannot get attrs\n"); - goto fail; - } - testres |= EC_TEST_CHECK( - ec_keyval_has_key(const_attrs, "key"), "cannot get attrs key\n"); - - 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_keyval_free(attrs); - 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 deleted file mode 100644 index cabe6b2..0000000 --- a/libecoli/ecoli_strvec.h +++ /dev/null @@ -1,224 +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); - -/** - * Set a string in the vector at specified index. - * - * @param strvec - * The pointer to the string vector. - * @param idx - * The index of the string to set. - * @param s - * The string to be set. - * @return - * 0 on success or -1 on error (errno is set). - */ -int ec_strvec_set(struct ec_strvec *strvec, size_t idx, const char *s); - -/** - * 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. - * - * Attributes are duplicated if any. - * - * @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. - * - * Attributes are duplicated if any. - * - * @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); - -/** - * Get the attributes of a vector element. - * - * @param strvec - * The pointer to the string vector. - * @param idx - * The index of the string to get. - * @return - * The read-only attributes (dictionnary) of the string at specified - * index, or NULL if there is no attribute. - */ -const struct ec_keyval *ec_strvec_get_attrs(const struct ec_strvec *strvec, - size_t idx); - -/** - * Set the attributes of a vector element. - * - * @param strvec - * The pointer to the string vector. - * @param idx - * The index of the string to get. - * @param attrs - * The attributes to be set. - * @return - * 0 on success, -1 on error (errno is set). On error, attrs - * are freed and must not be used by the caller. - */ -int ec_strvec_set_attrs(struct ec_strvec *strvec, size_t idx, - struct ec_keyval *attrs); - -/** - * 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. - * - * Attributes are not compared. - * - * @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 deleted file mode 100644 index b090bd3..0000000 --- a/libecoli/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/libecoli/ecoli_test.h b/libecoli/ecoli_test.h deleted file mode 100644 index 94cd0b9..0000000 --- a/libecoli/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/libecoli/ecoli_utils.h b/libecoli/ecoli_utils.h deleted file mode 100644 index 5a14192..0000000 --- a/libecoli/ecoli_utils.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -#ifndef ECOLI_UTILS_ -#define ECOLI_UTILS_ - -/** - * Cast a variable into a type, ensuring its initial type first - */ -#define EC_CAST(x, old_type, new_type) ({ \ - old_type __x = (x); \ - (new_type)__x; \ - }) - -#endif diff --git a/libecoli/ecoli_vec.c b/libecoli/ecoli_vec.c deleted file mode 100644 index fe8c572..0000000 --- a/libecoli/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/libecoli/ecoli_vec.h b/libecoli/ecoli_vec.h deleted file mode 100644 index 5fdaa99..0000000 --- a/libecoli/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/libecoli_editline/ecoli_editline.c b/libecoli_editline/ecoli_editline.c deleted file mode 100644 index 4cc7ec4..0000000 --- a/libecoli_editline/ecoli_editline.c +++ /dev/null @@ -1,684 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -struct ec_editline { - EditLine *el; - History *history; - HistEvent histev; - const struct ec_node *node; - char *prompt; -}; - -/* used by qsort below */ -static int -strcasecmp_cb(const void *p1, const void *p2) -{ - return strcasecmp(*(char * const *)p1, *(char * const *)p2); -} - -/* Show the matches as a multi-columns list */ -int -ec_editline_print_cols(struct ec_editline *editline, - char const * const *matches, size_t n) -{ - size_t max_strlen = 0, len, i, j, ncols; - int width, height; - const char *space; - char **matches_copy = NULL; - FILE *f; - - if (el_get(editline->el, EL_GETFP, 1, &f)) - return -1; - - fprintf(f, "\n"); - if (n == 0) - return 0; - - if (el_get(editline->el, EL_GETTC, "li", &height, (void *)0)) - return -1; - if (el_get(editline->el, EL_GETTC, "co", &width, (void *)0)) - return -1; - - /* duplicate the matches table, and sort it */ - matches_copy = calloc(n, sizeof(const char *)); - if (matches_copy == NULL) - return -1; - memcpy(matches_copy, matches, sizeof(const char *) * n); - qsort(matches_copy, n, sizeof(char *), strcasecmp_cb); - - /* get max string length */ - for (i = 0; i < n; i++) { - len = strlen(matches_copy[i]); - if (len > max_strlen) - max_strlen = len; - } - - /* write the columns */ - ncols = width / (max_strlen + 4); - if (ncols == 0) - ncols = 1; - for (i = 0; i < n; i+= ncols) { - for (j = 0; j < ncols; j++) { - if (i + j >= n) - break; - if (j == 0) - space = ""; - else - space = " "; - fprintf(f, "%s%-*s", space, - (int)max_strlen, matches[i+j]); - } - fprintf(f, "\n"); - } - - free(matches_copy); - return 0; -} - -/* Show the helps on editline output */ -int -ec_editline_print_helps(struct ec_editline *editline, - const struct ec_editline_help *helps, size_t len) -{ - size_t i; - FILE *out; - - if (el_get(editline->el, EL_GETFP, 1, &out)) - return -1; - - for (i = 0; i < len; i++) { - if (fprintf(out, "%-20s %s\n", - helps[i].desc, helps[i].help) < 0) - return -1; - } - - return 0; -} - -void -ec_editline_free_helps(struct ec_editline_help *helps, size_t len) -{ - size_t i; - - if (helps == NULL) - return; - for (i = 0; i < len; i++) { - ec_free(helps[i].desc); - ec_free(helps[i].help); - } - ec_free(helps); -} - -int -ec_editline_set_prompt(struct ec_editline *editline, const char *prompt) -{ - char *copy = NULL; - - if (prompt != NULL) { - ec_strdup(prompt); - if (copy == NULL) - return -1; - } - - ec_free(editline->prompt); - editline->prompt = copy; - - return 0; -} - -static char * -prompt_cb(EditLine *el) -{ - struct ec_editline *editline; - void *clientdata; - - if (el_get(el, EL_CLIENTDATA, &clientdata)) - return "> "; - editline = clientdata; - - if (editline == NULL) - return "> "; - - return editline->prompt; -} - -struct ec_editline * -ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err, - unsigned int flags) -{ - struct ec_editline *editline = NULL; - EditLine *el; - - if (f_in == NULL || f_out == NULL || f_err == NULL) { - errno = EINVAL; - goto fail; - } - - editline = ec_calloc(1, sizeof(*editline)); - if (editline == NULL) - goto fail; - - el = el_init(name, f_in, f_out, f_err); - if (el == NULL) - goto fail; - editline->el = el; - - /* save editline pointer as user data */ - if (el_set(el, EL_CLIENTDATA, editline)) - goto fail; - - /* install default editline signals */ - if (el_set(el, EL_SIGNAL, 1)) - goto fail; - - if (el_set(el, EL_PREP_TERM, 0)) - goto fail; - - /* use emacs bindings */ - if (el_set(el, EL_EDITOR, "emacs")) - goto fail; - if (el_set(el, EL_BIND, "^W", "ed-delete-prev-word", NULL)) - goto fail; - - /* ask terminal to not send signals */ - if (flags & EC_EDITLINE_DISABLE_SIGNALS) { - if (el_set(el, EL_SETTY, "-d", "-isig", NULL)) - goto fail; - } - - /* set prompt */ - editline->prompt = ec_strdup("> "); - if (editline->prompt == NULL) - goto fail; - if (el_set(el, EL_PROMPT, prompt_cb)) - goto fail; - - /* set up history */ - if ((flags & EC_EDITLINE_DISABLE_HISTORY) == 0) { - if (ec_editline_set_history( - editline, EC_EDITLINE_HISTORY_SIZE) < 0) - goto fail; - } - - /* register completion callback */ - if ((flags & EC_EDITLINE_DISABLE_COMPLETION) == 0) { - if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer", - ec_editline_complete)) - goto fail; - if (el_set(el, EL_BIND, "^I", "ed-complete", NULL)) - goto fail; - if (el_set(el, EL_BIND, "?", "ed-complete", NULL)) - goto fail; - } - - return editline; - -fail: - ec_editline_free(editline); - return NULL; -} - -void ec_editline_free(struct ec_editline *editline) -{ - if (editline == NULL) - return; - if (editline->el != NULL) - el_end(editline->el); - if (editline->history != NULL) - history_end(editline->history); - ec_free(editline->prompt); - ec_free(editline); -} - -EditLine *ec_editline_get_el(struct ec_editline *editline) -{ - return editline->el; -} - -const struct ec_node * -ec_editline_get_node(struct ec_editline *editline) -{ - return editline->node; -} - -void -ec_editline_set_node(struct ec_editline *editline, const struct ec_node *node) -{ - editline->node = node; -} - -int ec_editline_set_history(struct ec_editline *editline, - size_t hist_size) -{ - EditLine *el = editline->el; - - if (editline->history != NULL) - history_end(editline->history); - - if (hist_size == 0) - return 0; - - editline->history = history_init(); - if (editline->history == NULL) - goto fail; - if (history(editline->history, &editline->histev, H_SETSIZE, - hist_size) < 0) - goto fail; - if (history(editline->history, &editline->histev, H_SETUNIQUE, 1)) - goto fail; - if (el_set(el, EL_HIST, history, editline->history)) - goto fail; - - return 0; - -fail: - //XXX errno - if (editline->history != NULL) { - history_end(editline->history); - editline->history = NULL; - } - return -1; -} - -void ec_editline_free_completions(char **matches, size_t len) -{ - size_t i; - - // XXX use ec_malloc/ec_free() instead for consistency - if (matches == NULL) - return; - for (i = 0; i < len; i++) - free(matches[i]); - free(matches); -} - -ssize_t -ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out) -{ - const struct ec_comp_item *item; - struct ec_comp_iter *iter = NULL; - char **matches = NULL; - size_t count = 0; - - iter = ec_comp_iter(cmpl, EC_COMP_FULL | EC_COMP_PARTIAL); - if (iter == NULL) - goto fail; - - while ((item = ec_comp_iter_next(iter)) != NULL) { - char **tmp; - - tmp = realloc(matches, (count + 1) * sizeof(char *)); - if (tmp == NULL) - goto fail; - matches = tmp; - matches[count] = strdup(ec_comp_item_get_display(item)); - if (matches[count] == NULL) - goto fail; - count++; - } - - *matches_out = matches; - return count; - -fail: - ec_editline_free_completions(matches, count); - *matches_out = NULL; - ec_comp_iter_free(iter); - return -1; -} - -char * -ec_editline_append_chars(const struct ec_comp *cmpl) -{ - const struct ec_comp_item *item; - struct ec_comp_iter *iter = NULL; - const char *append; - char *ret = NULL; - size_t n; - - iter = ec_comp_iter(cmpl, EC_COMP_FULL | EC_COMP_PARTIAL); - if (iter == NULL) - goto fail; - - while ((item = ec_comp_iter_next(iter)) != NULL) { - append = ec_comp_item_get_completion(item); - if (ret == NULL) { - ret = ec_strdup(append); - if (ret == NULL) - goto fail; - } else { - n = ec_strcmp_count(ret, append); - ret[n] = '\0'; - } - } - ec_comp_iter_free(iter); - - return ret; - -fail: - ec_comp_iter_free(iter); - ec_free(ret); - - return NULL; -} - -/* this function builds the help string */ -static int get_node_help(const struct ec_comp_item *item, - struct ec_editline_help *help) -{ - const struct ec_comp_group *grp; - const struct ec_parse *state; - const struct ec_node *node; - const char *node_help = NULL; - const char *node_desc = NULL; - - help->desc = NULL; - help->help = NULL; - - grp = ec_comp_item_get_grp(item); - - 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) - goto fail; - - help->desc = ec_strdup(node_desc); - if (help->desc == NULL) - goto fail; - - help->help = ec_strdup(node_help); - if (help->help == NULL) - goto fail; - - return 0; - -fail: - ec_free(help->desc); - ec_free(help->help); - return -1; -} - -ssize_t -ec_editline_get_helps(const struct ec_editline *editline, const char *line, - const char *full_line, struct ec_editline_help **helps_out) -{ - struct ec_comp_iter *iter = NULL; - const struct ec_comp_group *grp, *prev_grp = NULL; - const struct ec_comp_item *item; - struct ec_comp *cmpl = NULL; - struct ec_parse *parse = NULL; - unsigned int count = 0; - struct ec_editline_help *helps = NULL; - - *helps_out = NULL; - - /* check if the current line matches */ - parse = ec_node_parse(editline->node, full_line); - if (ec_parse_matches(parse)) - count = 1; - ec_parse_free(parse); - parse = NULL; - - /* complete at current cursor position */ - cmpl = ec_node_complete(editline->node, line); - if (cmpl == NULL) //XXX log error - goto fail; - - /* let's display one contextual help per node */ - iter = ec_comp_iter(cmpl, - EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL); - if (iter == NULL) - goto fail; - - helps = ec_calloc(1, sizeof(*helps)); - if (helps == NULL) - goto fail; - if (count == 1) { - helps[0].desc = ec_strdup(""); - if (helps[0].desc == NULL) - goto fail; - helps[0].help = ec_strdup("Validate command."); - if (helps[0].help == NULL) - goto fail; - } - - while ((item = ec_comp_iter_next(iter)) != NULL) { - struct ec_editline_help *tmp = NULL; - - /* keep one help per group, skip other items */ - grp = ec_comp_item_get_grp(item); - if (grp == prev_grp) - continue; - - prev_grp = grp; - - tmp = ec_realloc(helps, (count + 1) * sizeof(*helps)); - if (tmp == NULL) - goto fail; - helps = tmp; - if (get_node_help(item, &helps[count]) < 0) - goto fail; - count++; - } - - ec_comp_iter_free(iter); - ec_comp_free(cmpl); - *helps_out = helps; - - return count; - -fail: - ec_comp_iter_free(iter); - ec_parse_free(parse); - ec_comp_free(cmpl); - if (helps != NULL) { - while (count--) { - ec_free(helps[count].desc); - ec_free(helps[count].help); - } - ec_free(helps); - } - - return -1; -} - -int -ec_editline_complete(EditLine *el, int c) -{ - struct ec_editline *editline; - const LineInfo *line_info; - int ret = CC_REFRESH; - struct ec_comp *cmpl = NULL; - char *append = NULL; - char *line = NULL; - void *clientdata; - FILE *out, *err; - int len; - - if (el_get(el, EL_GETFP, 1, &out)) - return -1; - if (el_get(el, EL_GETFP, 1, &err)) - return -1; - - (void)c; - - if (el_get(el, EL_CLIENTDATA, &clientdata)) { - fprintf(err, "completion failure: no client data\n"); - goto fail; - } - editline = clientdata; - (void)editline; - - line_info = el_line(el); - if (line_info == NULL) { - fprintf(err, "completion failure: no line info\n"); - goto fail; - } - - len = line_info->cursor - line_info->buffer; - if (ec_asprintf(&line, "%.*s", len, line_info->buffer) < 0) { - fprintf(err, "completion failure: no memory\n"); - goto fail; - } - - if (editline->node == NULL) { - fprintf(err, "completion failure: no ec_node\n"); - goto fail; - } - - cmpl = ec_node_complete(editline->node, line); - if (cmpl == NULL) - goto fail; - - append = ec_editline_append_chars(cmpl); - - if (c == '?') { - struct ec_editline_help *helps = NULL; - ssize_t count = 0; - - count = ec_editline_get_helps(editline, line, line_info->buffer, - &helps); - - fprintf(out, "\n"); - if (ec_editline_print_helps(editline, helps, count) < 0) { - fprintf(err, "completion failure: cannot show help\n"); - ec_editline_free_helps(helps, count); - goto fail; - } - - ec_editline_free_helps(helps, count); - ret = CC_REDISPLAY; - } else if (append == NULL || strcmp(append, "") == 0) { - char **matches = NULL; - ssize_t count = 0; - - count = ec_editline_get_completions(cmpl, &matches); - if (count < 0) { - fprintf(err, "completion failure: cannot get completions\n"); - goto fail; - } - - if (ec_editline_print_cols( - editline, - EC_CAST(matches, char **, - char const * const *), - count) < 0) { - fprintf(err, "completion failure: cannot print\n"); - ec_editline_free_completions(matches, count); - goto fail; - } - - ec_editline_free_completions(matches, count); - ret = CC_REDISPLAY; - } else { - if (el_insertstr(el, append) < 0) { - fprintf(err, "completion failure: cannot insert\n"); - goto fail; - } - if (ec_comp_count(cmpl, EC_COMP_FULL) + - ec_comp_count(cmpl, EC_COMP_PARTIAL) == 1) { - if (el_insertstr(el, " ") < 0) { - fprintf(err, "completion failure: cannot insert space\n"); - goto fail; - } - } - } - - ec_comp_free(cmpl); - ec_free(line); - ec_free(append); - - return ret; - -fail: - ec_comp_free(cmpl); - ec_free(line); - ec_free(append); - - return CC_ERROR; -} - -char * -ec_editline_gets(struct ec_editline *editline) -{ - EditLine *el = editline->el; - char *line_copy = NULL; - const char *line; - int count; - - line = el_gets(el, &count); - if (line == NULL) - return NULL; - - line_copy = ec_strdup(line); - if (line_copy == NULL) - goto fail; - - line_copy[strlen(line_copy) - 1] = '\0'; //XXX needed because of sh_lex bug? - - if (editline->history != NULL && !ec_str_is_space(line_copy)) { - history(editline->history, &editline->histev, - H_ENTER, line_copy); - } - - return line_copy; - -fail: - ec_free(line_copy); - return NULL; -} - -struct ec_parse * -ec_editline_parse(struct ec_editline *editline, const struct ec_node *node) -{ - char *line = NULL; - struct ec_parse *parse = NULL; - - /* XXX add sh_lex automatically? This node is required, parse and - * complete are based on it. */ - - ec_editline_set_node(editline, node); - - line = ec_editline_gets(editline); - if (line == NULL) - goto fail; - - parse = ec_node_parse(node, line); - if (parse == NULL) - goto fail; - - ec_free(line); - return parse; - -fail: - ec_free(line); - ec_parse_free(parse); - - return NULL; -} - diff --git a/libecoli_editline/ecoli_editline.h b/libecoli_editline/ecoli_editline.h deleted file mode 100644 index 58824d2..0000000 --- a/libecoli_editline/ecoli_editline.h +++ /dev/null @@ -1,163 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -/** - * Helpers that can be used to associate an editline instance with - * an ecoli node tree. - * - * XXX support saved history - * XXX support multiline edition - * XXX set prompt - */ - -#ifndef ECOLI_EDITLINE_ -#define ECOLI_EDITLINE_ - -#include - -struct ec_editline; -struct ec_node; -struct ec_parse; -struct ec_comp; - -struct ec_editline_help { - char *desc; - char *help; -}; - -/** - * Default history size. - */ -#define EC_EDITLINE_HISTORY_SIZE 128 - -/** - * Ask the terminal to not send signals (STOP, SUSPEND, XXX). The - * ctrl-c, ctrl-z will be interpreted as standard characters. An - * action can be associated to these characters with: - * - * static int cb(EditLine *editline, int c) { - * { - * see editline documentation for details - * } - * - * if (el_set(el, EL_ADDFN, "ed-foobar", "Help string about foobar", cb)) - * handle_error; - * if (el_set(el, EL_BIND, "^C", "ed-break", NULL)) - * handle_error; - * - * The default behavior (without this flag) is to let the signal pass: ctrl-c - * will stop program and ctrl-z will suspend it. - */ -#define EC_EDITLINE_DISABLE_SIGNALS 0x01 - -/** - * Disable history. The default behavior creates an history with - * EC_EDITLINE_HISTORY_SIZE entries. To change this value, use - * ec_editline_set_history(). - */ -#define EC_EDITLINE_DISABLE_HISTORY 0x02 - -/** - * Disable completion. The default behavior is to complete when - * '?' or '' is hit. You can register your own callback with: - * - * if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer", callback)) - * handle_error; - * if (el_set(el, EL_BIND, "^I", "ed-complete", NULL)) - * handle_error; - * - * The default used callback is ec_editline_complete(). - */ -#define EC_EDITLINE_DISABLE_COMPLETION 0x04 - -typedef int (*ec_editline_cmpl_t)(struct ec_editline *editline, int c); - -/** - * Create an editline instance with default behavior. - * - * XXX Wrapper to editline's el_init() - * - * It - */ -struct ec_editline * -ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err, - unsigned int flags); - -/** - * Free an editline instance allocated with ec_editline(). - */ -void ec_editline_free(struct ec_editline *editline); - -/** - * Return the editline instance attached to the ec_editline object. - */ -EditLine *ec_editline_get_el(struct ec_editline *editline); - -// XXX public? -const struct ec_node *ec_editline_get_node(struct ec_editline *editline); -void ec_editline_set_node(struct ec_editline *editline, - const struct ec_node *node); - -//XXX get history, get_... - -/** - * Change the history size. - * - * The default behavior is to have an history whose size - * is EC_EDITLINE_HISTORY_SIZE. This can be changed with this - * function. - * - * @param editline - * The pointer to the ec_editline structure. - * @param hist_size - * The desired size of the history. - * @return - * 0 on success, or -1 on error (errno is set). - */ -int ec_editline_set_history(struct ec_editline *editline, - size_t hist_size); - -int -ec_editline_print_cols(struct ec_editline *editline, - char const * const *matches, size_t n); - -void ec_editline_free_completions(char **matches, size_t len); -ssize_t -ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out); -char * -ec_editline_append_chars(const struct ec_comp *cmpl); - -ssize_t -ec_editline_get_helps(const struct ec_editline *editline, const char *line, - const char *full_line, struct ec_editline_help **helps_out); -int -ec_editline_print_helps(struct ec_editline *editline, - const struct ec_editline_help *helps, size_t n); -void -ec_editline_free_helps(struct ec_editline_help *helps, size_t len); - -int -ec_editline_set_prompt(struct ec_editline *editline, const char *prompt); - - - - -/** - * Get a line. - * - * The returned line must be freed by the caller using ec_free(). - */ -char *ec_editline_gets(struct ec_editline *editline); - -/** - * Get a line (managing completion) and parse it with passed node - * XXX find a better name? - */ -struct ec_parse * -ec_editline_parse(struct ec_editline *editline, const struct ec_node *node); - -int -ec_editline_complete(EditLine *el, int c); - -#endif diff --git a/libecoli_editline/editline.c b/libecoli_editline/editline.c deleted file mode 100644 index 449aebb..0000000 --- a/libecoli_editline/editline.c +++ /dev/null @@ -1,544 +0,0 @@ -/* - * Copyright 2018 6WIND S.A. - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "string_utils.h" -#include "editline.h" - -#define NC_CLI_HISTORY_SIZE 128 - -struct nc_cli_editline { - EditLine *editline; - History *history; - HistEvent histev; - bool break_received; - nc_cli_editline_complete_t complete; - FILE *null_out; - bool interactive; - bool incomplete_line; - char *full_line; - char *(*prompt_cb)(EditLine *); -}; - -struct nc_cli_editline *nc_cli_el; - -static int check_quotes(const char *str) -{ - char quote = 0; - size_t i = 0; - - while (str[i] != '\0') { - if (quote == 0) { - if (str[i] == '"' || str[i] == '\'') { - quote = str[i]; - } - i++; - continue; - } else { - if (str[i] == quote) { - i++; - quote = 0; - } else if (str[i] == '\\' && str[i+1] == quote) { - i += 2; - } else { - i++; - } - continue; - } - } - - return quote; -} - -static int -editline_break(EditLine *editline, int c) -{ - struct nc_cli_editline *el; - void *ptr; - - (void)c; - - if (el_get(editline, EL_CLIENTDATA, &ptr)) - return CC_ERROR; - - el = ptr; - el->break_received = true; - nc_cli_printf(el, "\n"); - - return CC_EOF; -} - -static int -editline_suspend(EditLine *editline, int c) -{ - (void)editline; - (void)c; - - kill(getpid(), SIGSTOP); - - return CC_NORM; -} - -int -editline_get_screen_size(struct nc_cli_editline *el, size_t *rows, size_t *cols) -{ - int w, h; - - if (rows != NULL) { - if (el_get(el->editline, EL_GETTC, "li", &h, (void *)0)) - return -1; - *rows = h; - } - if (cols != NULL) { - if (el_get(el->editline, EL_GETTC, "co", &w, (void *)0)) - return -1; - *cols = w; - } - return 0; -} - -FILE * -nc_cli_editline_get_file(struct nc_cli_editline *el, int num) -{ - FILE *f; - - if (el == NULL) - return NULL; - if (num > 2) - return NULL; - if (el_get(el->editline, EL_GETFP, num, &f)) - return NULL; - - return f; -} - -/* match the prototype expected by qsort() */ -static int -strcasecmp_cb(const void *p1, const void *p2) -{ - return strcasecmp(*(char * const *)p1, *(char * const *)p2); -} - -/* Show the matches as a multi-columns list */ -int -nc_cli_editline_print_cols(struct nc_cli_editline *el, - char const * const *matches, size_t n) -{ - size_t max_strlen = 0, len, i, j, ncols; - size_t width, height; - const char *space; - char **matches_copy = NULL; - - nc_cli_printf(nc_cli_el, "\n"); - if (n == 0) - return 0; - - if (editline_get_screen_size(el, &height, &width) < 0) - width = 80; - - /* duplicate the matches table, and sort it */ - matches_copy = calloc(n, sizeof(const char *)); - if (matches_copy == NULL) - return -1; - memcpy(matches_copy, matches, sizeof(const char *) * n); - qsort(matches_copy, n, sizeof(char *), strcasecmp_cb); - - /* get max string length */ - for (i = 0; i < n; i++) { - len = strlen(matches_copy[i]); - if (len > max_strlen) - max_strlen = len; - } - - /* write the columns */ - ncols = width / (max_strlen + 4); - if (ncols == 0) - ncols = 1; - for (i = 0; i < n; i+= ncols) { - for (j = 0; j < ncols; j++) { - if (i + j >= n) - break; - if (j == 0) - space = ""; - else - space = " "; - nc_cli_printf(nc_cli_el, "%s%-*s", space, - (int)max_strlen, matches[i+j]); - } - nc_cli_printf(nc_cli_el, "\n"); - } - - free(matches_copy); - return 0; -} - -static int -editline_complete(EditLine *editline, int c) -{ - enum nc_cli_editline_complete_status ret; - const LineInfo *line_info; - struct nc_cli_editline *el; - int len; - char *line; - void *ptr; - - if (el_get(editline, EL_CLIENTDATA, &ptr)) - return CC_ERROR; - - el = ptr; - - if (el->complete == NULL) - return CC_NORM; - - line_info = el_line(editline); - if (line_info == NULL) - return CC_ERROR; - - len = line_info->cursor - line_info->buffer; - if (asprintf(&line, "%s%.*s", el->full_line ? : "", len, - line_info->buffer) < 0) - return CC_ERROR; - - if (c == '?' && check_quotes(line) != 0) { - free(line); - el_insertstr(editline, "?"); - return CC_REFRESH; - } - - ret = el->complete(c, line); - free(line); - - if (ret == ERROR) - return CC_ERROR; - else if (ret == REDISPLAY) - return CC_REDISPLAY; - else - return CC_REFRESH; -} - -static bool is_blank_string(const char *s) -{ - while (*s) { - if (!isspace(*s)) - return false; - s++; - } - return true; -} - -static char * -multiline_prompt_cb(EditLine *e) -{ - (void)e; - return strdup("... "); -} - -const char * -nc_cli_editline_edit(struct nc_cli_editline *el) -{ - const char *line; - int count; - - assert(el->editline != NULL); - - if (el->incomplete_line == false) { - free(el->full_line); - el->full_line = NULL; - } - - el->break_received = false; - - if (el->incomplete_line) - el_set(el->editline, EL_PROMPT, multiline_prompt_cb); - else - el_set(el->editline, EL_PROMPT, el->prompt_cb); - - line = el_gets(el->editline, &count); - - if (line == NULL && el->break_received) { - free(el->full_line); - el->full_line = NULL; - el->incomplete_line = false; - return ""; /* abort current line */ - } - - if (line == NULL || astrcat(&el->full_line, line) < 0) { - free(el->full_line); - el->full_line = NULL; - el->incomplete_line = false; - return NULL; /* error / eof */ - } - - if (check_quotes(el->full_line) != 0) { - el->incomplete_line = true; - return ""; - } - - el->incomplete_line = false; - if (el->history != NULL && !is_blank_string(el->full_line)) - history(el->history, &el->histev, - H_ENTER, el->full_line); - - return el->full_line; -} - -int -nc_cli_editline_register_complete(struct nc_cli_editline *el, - nc_cli_editline_complete_t complete) -{ - const char *name; - - if (el_set(el->editline, EL_ADDFN, "ed-complete", - "Complete buffer", - editline_complete)) - return -1; - - if (complete != NULL) - name = "ed-complete"; - else - name = "ed-unassigned"; - if (el_set(el->editline, EL_BIND, "^I", name, NULL)) - return -1; - - if (complete != NULL) - name = "ed-complete"; - else - name = "ed-insert"; - if (el_set(el->editline, EL_BIND, "?", name, NULL)) - return -1; - - el->complete = complete; - - return 0; -} - -int -nc_cli_editline_insert_str(struct nc_cli_editline *el, const char *str) -{ - return el_insertstr(el->editline, str); -} - -int nc_cli_printf(struct nc_cli_editline *el, const char *format, ...) -{ - FILE *out = nc_cli_editline_get_file(el, 1); - va_list ap; - int ret; - - if (out == NULL) - out = stdout; - - va_start(ap, format); - ret = vfprintf(out, format, ap); - va_end(ap); - - return ret; -} - -int nc_cli_eprintf(struct nc_cli_editline *el, const char *format, ...) -{ - FILE *out = nc_cli_editline_get_file(el, 2); - va_list ap; - int ret; - - if (out == NULL) - out = stderr; - - va_start(ap, format); - ret = vfprintf(out, format, ap); - va_end(ap); - - return ret; -} - -bool -nc_cli_editline_is_interactive(struct nc_cli_editline *el) -{ - return el->interactive; -} - -bool -nc_cli_editline_is_running(struct nc_cli_editline *el) -{ - return el == nc_cli_el; -} - -void -nc_cli_editline_start(struct nc_cli_editline *el) -{ - nc_cli_el = el; -} - -void -nc_cli_editline_stop(struct nc_cli_editline *el) -{ - if (el == nc_cli_el) - nc_cli_el = NULL; -} - -int -nc_cli_editline_getc(struct nc_cli_editline *el) -{ - char c; - - if (el->interactive == false) - return -1; - - if (el_getc(el->editline, &c) != 1) - return -1; - - return c; -} - -int -nc_cli_editline_resize(struct nc_cli_editline *el) -{ - el_resize(el->editline); - return 0; -} - -int nc_cli_editline_set_prompt_cb(struct nc_cli_editline *el, - char *(*prompt_cb)(EditLine *el)) -{ - el->prompt_cb = prompt_cb; - return 0; -} - -int -nc_cli_editline_mask_interrupts(bool do_mask) -{ - const char *setty = do_mask ? "-isig" : "+isig"; - - if (nc_cli_el == NULL) - return -1; - - if (nc_cli_el->interactive == false) - return 0; - - if (el_set(nc_cli_el->editline, EL_SETTY, "-d", setty, NULL)) - return -1; - - return 0; -} - -struct nc_cli_editline * -nc_cli_editline_init(FILE *f_in, FILE *f_out, FILE *f_err, bool interactive, - char *(*prompt_cb)(EditLine *el)) -{ - struct nc_cli_editline *el = NULL; - - el = calloc(1, sizeof(*el)); - if (el == NULL) - goto fail; - if (f_in == NULL) { - errno = EINVAL; - goto fail; - } - if (f_out == NULL || f_err == NULL) { - el->null_out = fopen("/dev/null", "w"); - if (el->null_out == NULL) - goto fail; - } - if (f_out == NULL) - f_out = el->null_out; - if (f_err == NULL) - f_err = el->null_out; - - el->interactive = interactive; - el->prompt_cb = prompt_cb; - - el->editline = el_init("nc-cli", f_in, f_out, f_err); - if (el->editline == NULL) - goto fail; - - if (el_set(el->editline, EL_SIGNAL, 1)) - goto fail; - - if (el_set(el->editline, EL_CLIENTDATA, el)) - goto fail; - - if (el_set(el->editline, EL_PROMPT, prompt_cb)) - goto fail; - - if (interactive == false) - goto end; - - if (el_set(el->editline, EL_PREP_TERM, 0)) - goto fail; - - if (el_set(el->editline, EL_EDITOR, "emacs")) - goto fail; - - if (el_set(el->editline, EL_SETTY, "-d", "-isig", NULL)) - goto fail; - if (el_set(el->editline, EL_ADDFN, "ed-break", - "Break and flush the buffer", - editline_break)) - goto fail; - if (el_set(el->editline, EL_BIND, "^C", "ed-break", NULL)) - goto fail; - if (el_set(el->editline, EL_ADDFN, "ed-suspend", - "Suspend the terminal", - editline_suspend)) - goto fail; - if (el_set(el->editline, EL_BIND, "^Z", "ed-suspend", NULL)) - goto fail; - if (el_set(el->editline, EL_BIND, "^W", "ed-delete-prev-word", NULL)) - goto fail; - - el->history = history_init(); - if (!el->history) - goto fail; - if (history(el->history, &el->histev, H_SETSIZE, - NC_CLI_HISTORY_SIZE) < 0) - goto fail; - if (history(el->history, &el->histev, - H_SETUNIQUE, 1)) - goto fail; - if (el_set(el->editline, EL_HIST, history, - el->history)) - goto fail; - -end: - return el; - -fail: - if (el != NULL) { - if (el->null_out != NULL) - fclose(el->null_out); - if (el->history != NULL) - history_end(el->history); - if (el->editline != NULL) - el_end(el->editline); - free(el); - } - return NULL; -} - -void -nc_cli_editline_free(struct nc_cli_editline *el) -{ - if (el == NULL) - return; - nc_cli_editline_stop(el); - if (el->null_out != NULL) - fclose(el->null_out); - if (el->history != NULL) - history_end(el->history); - el_end(el->editline); - free(el->full_line); - free(el); -} diff --git a/libecoli_yaml/ecoli_yaml.c b/libecoli_yaml/ecoli_yaml.c deleted file mode 100644 index 84007d5..0000000 --- a/libecoli_yaml/ecoli_yaml.c +++ /dev/null @@ -1,550 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -#include -#include -#include -#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 associations yaml_node <-> ec_node */ -struct enode_table { - struct pair *pair; - size_t len; -}; - -static struct ec_node * -parse_ec_node(struct enode_table *table, const yaml_document_t *document, - const yaml_node_t *ynode); - -static struct ec_config * -parse_ec_config_list(struct enode_table *table, - 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_table *table, - 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_table *table, - const yaml_node_t *ynode, struct ec_node *enode) -{ - struct pair *pair = NULL; - - pair = realloc(table->pair, (table->len + 1) * sizeof(*pair)); - if (pair == NULL) - return -1; - - ec_node_clone(enode); - pair[table->len].ynode = ynode; - pair[table->len].enode = enode; - table->pair = pair; - table->len++; - - return 0; -} - -static void -free_table(struct enode_table *table) -{ - size_t i; - - for (i = 0; i < table->len; i++) - ec_node_free(table->pair[i].enode); - free(table->pair); -} - -static struct ec_config * -parse_ec_config(struct enode_table *table, - 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(table, document, ynode); - if (enode == NULL) - goto fail; - config = ec_config_node(enode); - enode = NULL; - 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(table, 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(table, 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_table *table, - 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; - - 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(table, 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_table *table, - 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(table, 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_table *table, - 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; - char *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 = ec_strdup(value_str); - if (help == NULL) { - fprintf(stderr, "Failed to allocate help\n"); - goto fail; - } - } - } - - /* 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(table, ynode, enode) < 0) { - fprintf(stderr, "Cannot add node in table\n"); - goto fail; - } - - /* 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(table, schema, document, ynode); - if (config == NULL) - goto fail; - - if (ec_node_set_config(enode, config) < 0) { - config = NULL; /* freed */ - fprintf(stderr, "Failed to set config\n"); - goto fail; - } - config = NULL; /* freed */ - - if (help != NULL) { - if (ec_keyval_set(ec_node_attrs(enode), "help", help, - ec_free_func) < 0) { - fprintf(stderr, "Failed to set help\n"); - help = NULL; - goto fail; - } - help = NULL; - } - - /* add attributes (all as string) */ - //XXX - - return enode; - -fail: - ec_node_free(enode); - ec_config_free(config); - ec_free(help); - - return NULL; -} - -static struct ec_node * -parse_document(struct enode_table *table, - const yaml_document_t *document) -{ - yaml_node_t *node; - - node = document->nodes.start; - return parse_ec_node(table, document, node); -} - -struct ec_node * -ec_yaml_import(const char *filename) -{ - FILE *file; - yaml_parser_t parser; - yaml_document_t document; - struct ec_node *root = NULL; - struct enode_table table; - - memset(&table, 0, sizeof(table)); - - 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; - } - - root = parse_document(&table, &document); - if (root == NULL) { - fprintf(stderr, "Failed to parse document\n"); - goto fail; - } - - yaml_document_delete(&document); - yaml_parser_delete(&parser); - fclose(file); - free_table(&table); - - return root; - -fail: - yaml_document_delete(&document); -fail_no_doc: - yaml_parser_delete(&parser); - if (file != NULL) - fclose(file); - free_table(&table); - ec_node_free(root); - - return NULL; -} diff --git a/libecoli_yaml/ecoli_yaml.h b/libecoli_yaml/ecoli_yaml.h deleted file mode 100644 index a63d837..0000000 --- a/libecoli_yaml/ecoli_yaml.h +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright 2018, Olivier MATZ - */ - -/** - * Interface to import/export ecoli data structures in YAML. - */ - -#ifndef ECOLI_NODE_YAML_ -#define ECOLI_NODE_YAML_ - -struct ec_node; - -/** - * Parse a YAML file and build an ec_node tree from it. - * - * @param filename - * The path to the file to be parsed. - * @return - * The ec_node tree on success, or NULL on error (errno is set). - * The returned node must be freed by the caller with ec_node_free(). - */ -struct ec_node *ec_yaml_import(const char *filename); - -#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..dee9cb6 --- /dev/null +++ b/meson.build @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2018, Olivier MATZ + +project('libecoli', + 'c', + version : '0.1', + license : 'BSD-3-clause') + +# if debug +add_global_arguments('-Wall', language : 'c') +add_global_arguments('-Werror', language : 'c') +add_global_arguments('-W', language : 'c') +add_global_arguments('-Wextra', language : 'c') + +edit_dep = dependency('libedit', method: 'pkg-config') +yaml_dep = dependency('yaml-0.1', method: 'pkg-config') + +subdir('src') +subdir('test') +subdir('examples') + +pkg_mod = import('pkgconfig') +pkg_mod.generate(libraries : libecoli, + version: '0.1', + name : 'libecoli', + filebase : 'libecoli', + description : 'Extensible command line library.') diff --git a/mk/ecoli-ar-rules.mk b/mk/ecoli-ar-rules.mk deleted file mode 100644 index 9121d4f..0000000 --- a/mk/ecoli-ar-rules.mk +++ /dev/null @@ -1,31 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# dump some infos if debug is enabled -ifeq ($(D),1) -$(call disp_list,------ all-ar,$(all-ar)) -$(foreach ar,$(all-ar),\ - $(info,out-$(ar): $(out-$(ar))) \ - $(call disp_list,pre-$(ar),$(pre-$(ar))) \ -) -endif - -# include dependencies and commands files if they exist -$(foreach ar,$(all-ar),\ - $(eval -include $(call depfile,$(ar))) \ - $(eval -include $(call cmdfile,$(ar))) \ -) - -# remove duplicates -filtered-all-ar := $(sort $(all-ar)) - -# link several objects files into one shared object -$(filtered-all-ar): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,\ - $(call ar_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call ar_cmd,$(pre-$(@)),$@),$?),\ - $(call ar_print_cmd,$(pre-$(@)),$@) && \ - $(call ar_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call ar_cmd,$(pre-$(@)),$@),$@) && \ - $(call create_empty_depfile,$@)) diff --git a/mk/ecoli-ar-vars.mk b/mk/ecoli-ar-vars.mk deleted file mode 100644 index 0a06b26..0000000 --- a/mk/ecoli-ar-vars.mk +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# ar-y-$(ar) is provided by the user -# $(ar) is the path of the static library, and the variable contains -# the list of sources. Several ar-y-$(ar) can be present. - -# list all ar builds requested by user -all-ar := $(patsubst ar-y-%,%,$(filter ar-y-%,$(.VARIABLES))) - -# add them to the list of targets -all-targets += $(all-ar) - -# for each ar, create the following variables: -# out-$(ar) = output path of the arcutable -# pre-$(ar) = list of prerequisites for this arcutable -# Some source files need intermediate objects, we define these variables -# for them too, and add them in a list: $(all-iobj). -# Last, we add the generated files in $(all-clean-file). -$(foreach ar,$(all-ar),\ - $(eval out-$(ar) := $(dir $(ar))) \ - $(eval pre-$(ar) := ) \ - $(foreach src,$(ar-y-$(ar)), \ - $(if $(call is_cc_source,$(src)), \ - $(eval iobj := $(call src2iobj,$(src),$(out-$(ar)))) \ - $(eval pre-$(iobj) := $(src)) \ - $(eval all-iobj += $(iobj)) \ - $(eval all-clean-file += $(iobj)) \ - $(eval pre-$(ar) += $(iobj)) \ - , \ - $(if $(call is_obj_source,$(src)),\ - $(eval pre-$(ar) += $(src)) \ - , \ - $(error "unsupported source format: $(src)"))) \ - )\ - $(eval all-clean-file += $(ar)) \ -) - -# link several *.o files into a static libary -# $1: sources (*.o) -# $2: dst (xyz.a) -ar_cmd = ar crsD $(2) $(1) - -# print line used to ar object files -ifeq ($(V),1) -ar_print_cmd = echo $(call protect_quote,$(call ar_cmd,$1,$2)) -else -ar_print_cmd = echo " AR $(2)" -endif - -all-clean-file += $(all-ar) diff --git a/mk/ecoli-clean-rules.mk b/mk/ecoli-clean-rules.mk deleted file mode 100644 index 6cdf02b..0000000 --- a/mk/ecoli-clean-rules.mk +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -.PHONY: _ecoli_clean -_ecoli_clean: $(all-clean-target) FORCE - @$(call clean_print_cmd,$(all-clean-file) $(call depfile,$(all-clean-file)) \ - $(call cmdfile,$(all-clean-file))) && \ - $(call clean_cmd,$(all-clean-file) $(call depfile,$(all-clean-file)) \ - $(call cmdfile,$(all-clean-file))) diff --git a/mk/ecoli-clean-vars.mk b/mk/ecoli-clean-vars.mk deleted file mode 100644 index 6b47154..0000000 --- a/mk/ecoli-clean-vars.mk +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# remove files -# $1: files -clean_cmd = rm -rf $(1) - -# print line used to clean files -ifeq ($(V),1) -clean_print_cmd = echo $(call protect_quote,$(call clean_cmd,$1)) -else -clean_print_cmd = echo " CLEAN $(CURDIR)" -endif diff --git a/mk/ecoli-copy-rules.mk b/mk/ecoli-copy-rules.mk deleted file mode 100644 index 3c3c43b..0000000 --- a/mk/ecoli-copy-rules.mk +++ /dev/null @@ -1,31 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# dump some infos if debug is enabled -ifeq ($(D),1) -$(call disp_list,------ all-copy,$(all-copy)) -$(foreach copy,$(all-copy),\ - $(info,out-$(copy): $(out-$(copy))) \ - $(call disp_list,pre-$(copy),$(pre-$(copy))) \ -) -endif - -# include dependencies and commands files if they exist -$(foreach copy,$(all-copy),\ - $(eval -include $(call depfile,$(copy))) \ - $(eval -include $(call cmdfile,$(copy))) \ -) - -# remove duplicates -filtered-all-copy := $(sort $(all-copy)) - -# convert format of executable -$(filtered-all-copy): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,\ - $(call copy_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call copy_cmd,$(pre-$(@)),$@),$?),\ - $(call copy_print_cmd,$(pre-$(@)),$@) && \ - $(call copy_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call copy_cmd,$(pre-$(@)),$@),$@) && \ - $(call create_empty_depfile,$@)) diff --git a/mk/ecoli-copy-vars.mk b/mk/ecoli-copy-vars.mk deleted file mode 100644 index d6230d7..0000000 --- a/mk/ecoli-copy-vars.mk +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# copy a file -# copy-y-$(copy) is provided by the user -# $(copy) is the path of the directory containing the destination -# files, and the variable contains the path of the files to copy. Several -# copy-y-$(copy) can be present. - -# list all path requested by user -_all-copy := $(patsubst copy-y-%,%,$(filter copy-y-%,$(.VARIABLES))) -all-copy := - -# for each copy, create the following variables: -# out-$(copy) = output path of the executable -# pre-$(copy) = list of prerequisites for this executable -# We also add the files in $(all-copy). -$(foreach copy,$(_all-copy),\ - $(if $(notdir $(copy)), \ - $(if $(call compare,$(words $(copy-y-$(copy))),1), \ - $(error "only one source file is allowed in copy-y-$(copy)")) \ - $(eval dst := $(dir $(copy))$(notdir $(copy-y-$(copy)))) \ - $(eval out-$(copy) := $(dir $(copy))) \ - $(eval pre-$(copy) := $(copy-y-$(copy))) \ - $(eval all-copy += $(dst)) \ - , \ - $(foreach src,$(copy-y-$(copy)),\ - $(eval dst := $(copy)$(notdir $(src))) \ - $(eval out-$(copy) := $(copy)) \ - $(eval pre-$(dst) := $(src)) \ - $(eval all-copy += $(dst)) \ - ) \ - ) \ -) - -# add them to the list of targets and clean -all-targets += $(all-copy) -all-clean-file += $(all-copy) - -# convert format of executable from elf to ihex -# $1: source executable (elf) -# $2: destination file -copy_cmd = $(CP) $(1) $(2) - -# print line used to convert executable format -ifeq ($(V),1) -copy_print_cmd = echo $(call protect_quote,$(call copy_cmd,$1,$2)) -else -copy_print_cmd = echo " COPY $(2)" -endif diff --git a/mk/ecoli-exe-rules.mk b/mk/ecoli-exe-rules.mk deleted file mode 100644 index 71b7ed1..0000000 --- a/mk/ecoli-exe-rules.mk +++ /dev/null @@ -1,31 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# dump some infos if debug is enabled -ifeq ($(D),1) -$(call disp_list,------ all-exe,$(all-exe)) -$(foreach exe,$(all-exe),\ - $(info,out-$(exe): $(out-$(exe))) \ - $(call disp_list,pre-$(exe),$(pre-$(exe))) \ -) -endif - -# include dependencies and commands files if they exist -$(foreach exe,$(all-exe),\ - $(eval -include $(call depfile,$(exe))) \ - $(eval -include $(call cmdfile,$(exe))) \ -) - -# remove duplicates -filtered-all-exe := $(sort $(all-exe)) - -# link several objects files into one executable -$(filtered-all-exe): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,\ - $(call link_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call link_cmd,$(pre-$(@)),$@),$?),\ - $(call link_print_cmd,$(pre-$(@)),$@) && \ - $(call link_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call link_cmd,$(pre-$(@)),$@),$@) && \ - $(call create_empty_depfile,$@)) diff --git a/mk/ecoli-exe-vars.mk b/mk/ecoli-exe-vars.mk deleted file mode 100644 index 1e5ae16..0000000 --- a/mk/ecoli-exe-vars.mk +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# exe-y-$(exe) is provided by the user -# $(exe) is the path of the binary, and the variable contains -# the list of sources. Several exe-y-$(exe) can be present. - -# list all exe builds requested by user -all-exe := $(patsubst exe-y-%,%,$(filter exe-y-%,$(.VARIABLES))) - -# add them to the list of targets -all-targets += $(all-exe) - -# for each exe, create the following variables: -# out-$(exe) = output path of the executable -# pre-$(exe) = list of prerequisites for this executable -# Some source files need intermediate objects, we define these variables -# for them too, and add them in a list: $(all-iobj). -# Last, we add the generated files in $(all-clean-file). -$(foreach exe,$(all-exe),\ - $(eval out-$(exe) := $(dir $(exe))) \ - $(eval pre-$(exe) := ) \ - $(foreach src,$(exe-y-$(exe)), \ - $(if $(call is_cc_source,$(src)), \ - $(eval iobj := $(call src2iobj,$(src),$(out-$(exe)))) \ - $(eval pre-$(iobj) := $(src)) \ - $(eval all-iobj += $(iobj)) \ - $(eval all-clean-file += $(iobj)) \ - $(eval pre-$(exe) += $(iobj)) \ - , \ - $(if $(call is_obj_source,$(src)),\ - $(eval pre-$(exe) += $(src)) \ - , \ - $(if $(call is_alib_source,$(src)),\ - $(eval pre-$(exe) += $(src)) \ - , \ - $(error "unsupported source format: $(src)")))) \ - )\ - $(eval all-clean-file += $(exe)) \ -) - -# link several *.o files into a exeary -# $1: sources (*.o) (*.a) -# $2: dst (xyz.o too) -link_cmd = $(CC) $(LDFLAGS) $(ldflags-$(2)) -o $(2) $(filter %.o,$(1)) \ - $(filter %.a,$(1)) $(LDLIBS) $(ldlibs-$(2)) - -# print line used to link object files -ifeq ($(V),1) -link_print_cmd = echo $(call protect_quote,$(call link_cmd,$1,$2)) -else -link_print_cmd = echo " EXE $(2)" -endif - -all-clean-file += $(all-exe) diff --git a/mk/ecoli-obj-rules.mk b/mk/ecoli-obj-rules.mk deleted file mode 100644 index 24c606f..0000000 --- a/mk/ecoli-obj-rules.mk +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# dump some infos if debug is enabled -ifeq ($(D),1) -$(call disp_list,------ all-obj,$(all-obj)) -$(foreach obj,$(all-obj),\ - $(info,out-$(obj): $(out-$(obj))) \ - $(call disp_list,pre-$(obj),$(pre-$(obj))) \ -) -$(call disp_list,------ all-iobj,$(all-iobj)) -$(foreach iobj,$(all-iobj),\ - $(call disp_list,pre-$(iobj),$(pre-$(iobj))) \ -) -endif - -# if a generated file has the same name than a user target, -# generate an error -conflicts := $(filter $(all-iobj),$(all-targets)) -$(if $(conflicts), \ - $(error Intermediate file has the same names than user targets:\ - $(conflicts))) - -# include dependencies and commands files if they exist -$(foreach obj,$(all-obj),\ - $(eval -include $(call depfile,$(obj))) \ - $(eval -include $(call cmdfile,$(obj))) \ -) -$(foreach iobj,$(all-iobj),\ - $(eval -include $(call depfile,$(iobj))) \ - $(eval -include $(call cmdfile,$(iobj))) \ -) - -# remove duplicates -filtered-all-iobj := $(sort $(all-iobj)) - -# convert source files to intermediate object file -$(filtered-all-iobj): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,$(call compile_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call compile_cmd,$(pre-$(@)),$@),$?),\ - $(call compile_print_cmd,$(pre-$(@)),$@) && \ - $(call compile_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call compile_cmd,$(pre-$(@)),$@),$@) && \ - $(call obj-fixdep,$@)) - -# remove duplicates -filtered-all-obj := $(sort $(all-obj)) - -# combine several objects files to one -$(filtered-all-obj): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,\ - $(call combine_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call combine_cmd,$(pre-$(@)),$@),$?),\ - $(call combine_print_cmd,$(pre-$(@)),$@) && \ - $(call combine_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call combine_cmd,$(pre-$(@)),$@),$@) && \ - $(call create_empty_depfile,$@)) diff --git a/mk/ecoli-obj-vars.mk b/mk/ecoli-obj-vars.mk deleted file mode 100644 index b6fe847..0000000 --- a/mk/ecoli-obj-vars.mk +++ /dev/null @@ -1,100 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# obj-y-$(obj) is provided by the user -# $(obj) is the path of the object, and the variable contains -# the list of sources. Several obj-y-$(obj) can be present. - -# list all object builds requested by user -all-obj := $(patsubst obj-y-%,%,$(filter obj-y-%,$(.VARIABLES))) - -# add them to the list of targets -all-targets += $(all-obj) - -# convert source path to intermediate object path, and filter -# objects from sources -# $1: list of source paths -# $2: output directory (including trailing slash) -# return: list of intermediate object paths -src2iobj = $(addprefix $(filter-out ./,$(2)),$(notdir $(strip \ - $(patsubst %.c,%.o,\ - $(patsubst %.s,%.o,\ - $(filter-out %.o,$(1))))))) - -# return the file if it matches a extension that is built with cc -# $1: source file -is_cc_source = $(filter %.c %.s %S,$(1)) - -# return the file if it's already an object file: in this case no -# intermediate object is needed -# $1: source file -is_obj_source = $(filter %.o,$(1)) - -# return the file if it's a static library -# $1: source file -is_alib_source = $(filter %.a,$(1)) - -# for each obj, create the following variables: -# out-$(obj) = output path of the object -# pre-$(obj) = list of prerequisites for this object -# Some source files need intermediate objects, we define these variables -# for them too, and add them in a list: $(all-iobj). -# Last, we add the generated files in $(all-clean-file). -$(foreach obj,$(all-obj),\ - $(eval out-$(obj) := $(dir $(obj))) \ - $(eval pre-$(obj) := ) \ - $(foreach src,$(obj-y-$(obj)), \ - $(if $(call is_cc_source,$(src)), \ - $(eval iobj := $(call src2iobj,$(src),$(out-$(obj)))) \ - $(eval pre-$(iobj) := $(src)) \ - $(eval all-iobj += $(iobj)) \ - $(eval all-clean-file += $(iobj)) \ - $(eval pre-$(obj) += $(iobj)) \ - , \ - $(if $(call is_obj_source,$(src)),\ - $(eval pre-$(obj) += $(src)) \ - , \ - $(error "unsupported source format: $(src)"))) \ - )\ - $(eval all-clean-file += $(obj)) \ -) - -# fix the format of .o.d.tmp (generated by gcc) to a .o.d that defines -# dependencies as makefile variables -# $1: object file (.o) -obj-fixdep = if [ -f $(call file2tmpdep,$(1)) ]; then\ - echo -n "dep-$(1) = " > $(call depfile,$(1)) && \ - sed 's,^[^ ][^:]*: ,,' $(call file2tmpdep,$(1)) >> $(call depfile,$(1)) && \ - rm -f $(call file2tmpdep,$(1)); \ - else \ - $(call create_empty_depfile,$(1)); \ - fi - -# compile a file -# $1: sources -# $2: dst -compile_cmd = $(CC) -Wp,-MD,$(call file2tmpdep,$(2)) \ - $(CPPFLAGS) $(cppflags-$(2)) \ - $(CFLAGS) $(cflags-$(2)) \ - -c -o $2 $1 - -# print line used to compile a file -ifeq ($(V),1) -compile_print_cmd = echo $(call protect_quote,$(call compile_cmd,$1,$2)) -else -compile_print_cmd = echo " CC $(2)" -endif - -# combine several *.o files into one -# $1: sources (*.o) -# $2: dst (xyz.o too) -combine_cmd = $(LD) -r $(1) -o $(2) - -# print line used to combine object files -ifeq ($(V),1) -combine_print_cmd = echo $(call protect_quote,$(call combine_cmd,$1,$2)) -else -combine_print_cmd = echo " LD $(2)" -endif - -all-clean-file += $(all-obj) diff --git a/mk/ecoli-objcopy-rules.mk b/mk/ecoli-objcopy-rules.mk deleted file mode 100644 index ebe9306..0000000 --- a/mk/ecoli-objcopy-rules.mk +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# dump some infos if debug is enabled -ifeq ($(D),1) -$(call disp_list,------ all-objcopy-hex,$(all-objcopy-hex)) -$(foreach objcopy,$(all-objcopy-hex),\ - $(info,out-$(objcopy): $(out-$(objcopy))) \ - $(call disp_list,pre-$(objcopy),$(pre-$(objcopy))) \ -) -$(call disp_list,------ all-objcopy-bin,$(all-objcopy-bin)) -$(foreach objcopy,$(all-objcopy-bin),\ - $(info,out-$(objcopy): $(out-$(objcopy))) \ - $(call disp_list,pre-$(objcopy),$(pre-$(objcopy))) \ -) -endif - -# include dependencies and commands files if they exist -$(foreach objcopy,$(all-objcopy-hex) $(all-objcopy-bin),\ - $(eval -include $(call depfile,$(objcopy))) \ - $(eval -include $(call cmdfile,$(objcopy))) \ -) - -# remove duplicates -filtered-all-objcopy-hex := $(sort $(all-objcopy-hex)) - -# convert format of executable -$(filtered-all-objcopy-hex): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,\ - $(call objcopy_hex_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call objcopy_hex_cmd,$(pre-$(@)),$@),$?),\ - $(call objcopy_print_cmd,$(pre-$(@)),$@) && \ - $(call objcopy_hex_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call objcopy_hex_cmd,$(pre-$(@)),$@),$@) && \ - $(call create_empty_depfile,$@)) - -# remove duplicates -filtered-all-objcopy-bin := $(sort $(all-objcopy-bin)) - -# convert format of executable -$(filtered-all-objcopy-bin): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,\ - $(call objcopy_bin_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call objcopy_bin_cmd,$(pre-$(@)),$@),$?),\ - $(call objcopy_print_cmd,$(pre-$(@)),$@) && \ - $(call objcopy_bin_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call objcopy_bin_cmd,$(pre-$(@)),$@),$@) && \ - $(call create_empty_depfile,$@)) diff --git a/mk/ecoli-objcopy-vars.mk b/mk/ecoli-objcopy-vars.mk deleted file mode 100644 index 0a4b486..0000000 --- a/mk/ecoli-objcopy-vars.mk +++ /dev/null @@ -1,65 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# objcopy changes the format of a binary -# objcopy-hex-y-$(objcopy), objcopy-bin-y-$(objcopy) are provided by the user -# $(objcopy) is the path of the binary, and the variable contains -# the path to the elf. Several objcopy-y-$(objcopy) can be present. - -# list all executable builds requested by user -all-objcopy-hex := $(patsubst objcopy-hex-y-%,%,$(filter objcopy-hex-y-%,$(.VARIABLES))) -all-objcopy-bin := $(patsubst objcopy-bin-y-%,%,$(filter objcopy-bin-y-%,$(.VARIABLES))) - -# add them to the list of targets -all-targets += $(all-objcopy-hex) $(all-objcopy-bin) - -# for each objcopy, create the following variables: -# out-$(objcopy) = output path of the executable -# pre-$(objcopy) = list of prerequisites for this executable -# We also add the generated files in $(all-clean-file). -$(foreach objcopy,$(all-objcopy-hex),\ - $(if $(call compare,$(words $(objcopy-hex-y-$(objcopy))),1),\ - $(error "only one source file is allowed in objcopy-hex-y-$(objcopy)")) \ - $(eval out-$(objcopy) := $(dir $(objcopy))) \ - $(eval pre-$(objcopy) := $(objcopy-hex-y-$(objcopy))) \ - $(eval all-clean-file += $(objcopy)) \ -) - -# for each objcopy, create the following variables: -# out-$(objcopy) = output path of the executable -# pre-$(objcopy) = list of prerequisites for this executable -# We also add the generated files in $(all-clean-file). -$(foreach objcopy,$(all-objcopy-bin),\ - $(if $(call compare,$(words $(objcopy-bin-y-$(objcopy))),1),\ - $(error "only one source file is allowed in objcopy-bin-y-$(objcopy)")) \ - $(eval out-$(objcopy) := $(dir $(objcopy))) \ - $(eval pre-$(objcopy) := $(objcopy-bin-y-$(objcopy))) \ - $(eval all-clean-file += $(objcopy)) \ -) - -# convert format of executable from elf to ihex -# $1: source executable (elf) -# $2: destination file -objcopy_hex_cmd = $(OBJCOPY) -O ihex $(1) $(2) - -# print line used to convert executable format -ifeq ($(V),1) -objcopy_print_cmd = echo $(call protect_quote,$(call objcopy_hex_cmd,$1,$2)) -else -objcopy_print_cmd = echo " OBJCOPY $(2)" -endif - -# convert format of executable from elf to binary -# $1: source executable (elf) -# $2: destination file -objcopy_bin_cmd = $(OBJCOPY) -O binary $(1) $(2) - -# print line used to convert executable format -ifeq ($(V),1) -objcopy_print_cmd = echo $(call protect_quote,$(call objcopy_bin_cmd,$1,$2)) -else -objcopy_print_cmd = echo " OBJCOPY $(2)" -endif - -# XXX dup ? -all-clean-file += $(all-objcopy-hex) $(all-objcopy-bin) diff --git a/mk/ecoli-post.mk b/mk/ecoli-post.mk deleted file mode 100644 index 7430673..0000000 --- a/mk/ecoli-post.mk +++ /dev/null @@ -1,84 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# ---- variables that must be defined: -# -# ECOLI: path to ecoli root -# -# ---- variable that can be defined anywhere -# -# CROSS: prefix of the toolchain -# CP, LN, GAWK, GREP: coreutils tools -# CC, CPP, AR, LD, OBJCOPY, OBJDUMP, STRIP: compilers/binutils -# -# ---- variable that can be defined by Makefile: -# -# obj-y-$(path) -# exe-y-$(path) -# ar-y-$(path) -# shlib-y-$(path) -# copy-y-$(path) -# slink-y-$(path) -# objcopy-y-$(path) -# subdir-y -# -# CPPFLAGS, CFLAGS, LDFLAGS, LDLIBS: global flags -# cflags-$(path), cppflags-$(path), ldflags-$(path), ldlibs-$(path): per -# file flags -# mkflags-$(path): flags for subdirectories -# -# ---- variables that can be defined on the command line: -# -# EXTRA_CPPFLAGS, EXTRA_CFLAGS, EXTRA_LDFLAGS, EXTRA_LDLIBS: global -# extra flags -# extra-cflags-$(path), extra-cppflags-$(path): per object extra flags - -ifeq ($(ECOLI),) -$(error ECOLI environment variable is not defined) -endif - -# list of targets asked by user -all-targets := -# list of files generated -all-clean-file := - -# usual internal variables: -# out-$(file) = output path of a generated file -# pre-$(file) = list of files needed to generate $(file) -# all-type = list of targets for this type - -include $(ECOLI)/mk/ecoli-obj-vars.mk -include $(ECOLI)/mk/ecoli-exe-vars.mk -include $(ECOLI)/mk/ecoli-ar-vars.mk -include $(ECOLI)/mk/ecoli-shlib-vars.mk -include $(ECOLI)/mk/ecoli-copy-vars.mk -include $(ECOLI)/mk/ecoli-slink-vars.mk -include $(ECOLI)/mk/ecoli-objcopy-vars.mk -include $(ECOLI)/mk/ecoli-subdir-vars.mk -# must stay at the end -include $(ECOLI)/mk/ecoli-clean-vars.mk - -# dump the list of targets -ifeq ($(D),1) -$(call disp_list,------ all-targets,$(all-targets)) -endif - -# first rule (default) -.PHONY: _ecoli_all -_ecoli_all: $(all-targets) - -# the includes below require second expansion -.SECONDEXPANSION: - -include $(ECOLI)/mk/ecoli-obj-rules.mk -include $(ECOLI)/mk/ecoli-exe-rules.mk -include $(ECOLI)/mk/ecoli-ar-rules.mk -include $(ECOLI)/mk/ecoli-shlib-rules.mk -include $(ECOLI)/mk/ecoli-copy-rules.mk -include $(ECOLI)/mk/ecoli-slink-rules.mk -include $(ECOLI)/mk/ecoli-objcopy-rules.mk -include $(ECOLI)/mk/ecoli-subdir-rules.mk -include $(ECOLI)/mk/ecoli-clean-rules.mk - -.PHONY: FORCE -FORCE: diff --git a/mk/ecoli-pre.mk b/mk/ecoli-pre.mk deleted file mode 100644 index 1d56ec2..0000000 --- a/mk/ecoli-pre.mk +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# ---- variables that must be defined: -# -# ECOLI: path to ecoli root -# - -ifeq ($(ECOLI),) -$(error ECOLI environment variable is not defined) -endif - -MAKEFLAGS += --no-print-directory - -include $(ECOLI)/mk/ecoli-tools.mk - -include $(ECOLI)/mk/ecoli-vars.mk - diff --git a/mk/ecoli-shlib-rules.mk b/mk/ecoli-shlib-rules.mk deleted file mode 100644 index b03d2a7..0000000 --- a/mk/ecoli-shlib-rules.mk +++ /dev/null @@ -1,31 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# dump some infos if debug is enabled -ifeq ($(D),1) -$(call disp_list,------ all-shlib,$(all-shlib)) -$(foreach shlib,$(all-shlib),\ - $(info,out-$(shlib): $(out-$(shlib))) \ - $(call disp_list,pre-$(shlib),$(pre-$(shlib))) \ -) -endif - -# include dependencies and commands files if they exist -$(foreach shlib,$(all-shlib),\ - $(eval -include $(call depfile,$(shlib))) \ - $(eval -include $(call cmdfile,$(shlib))) \ -) - -# remove duplicates -filtered-all-shlib := $(sort $(all-shlib)) - -# link several objects files into one shared object -$(filtered-all-shlib): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,\ - $(call shlib_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call shlib_cmd,$(pre-$(@)),$@),$?),\ - $(call shlib_print_cmd,$(pre-$(@)),$@) && \ - $(call shlib_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call shlib_cmd,$(pre-$(@)),$@),$@) && \ - $(call create_empty_depfile,$@)) diff --git a/mk/ecoli-shlib-vars.mk b/mk/ecoli-shlib-vars.mk deleted file mode 100644 index 55b982c..0000000 --- a/mk/ecoli-shlib-vars.mk +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# shlib-y-$(shlib) is provided by the user -# $(shlib) is the path of the shared library, and the variable -# contains the list of sources. Several shlib-y-$(shlib) can be -# present. - -# list all shlib builds requested by user -all-shlib := $(patsubst shlib-y-%,%,$(filter shlib-y-%,$(.VARIABLES))) - -# add them to the list of targets -all-targets += $(all-shlib) - -# for each shlib, create the following variables: -# out-$(shlib) = output path of the shlibcutable -# pre-$(shlib) = list of prerequisites for this shlibcutable -# Some source files need intermediate objects, we define these variables -# for them too, and add them in a list: $(all-iobj). -# Last, we add the generated files in $(all-clean-file). -$(foreach shlib,$(all-shlib),\ - $(eval out-$(shlib) := $(dir $(shlib))) \ - $(eval pre-$(shlib) := ) \ - $(foreach src,$(shlib-y-$(shlib)), \ - $(if $(call is_cc_source,$(src)), \ - $(eval iobj := $(call src2iobj,$(src),$(out-$(shlib)))) \ - $(eval pre-$(iobj) := $(src)) \ - $(eval all-iobj += $(iobj)) \ - $(eval all-clean-file += $(iobj)) \ - $(eval pre-$(shlib) += $(iobj)) \ - , \ - $(if $(call is_obj_source,$(src)),\ - $(eval pre-$(shlib) += $(src)) \ - , \ - $(error "unsupported source format: $(src)"))) \ - )\ - $(eval all-clean-file += $(shlib)) \ -) - -# link several *.o files into a shared libary -# $1: sources (*.o) -# $2: dst (xyz.so) -shlib_cmd = $(CC) $(LDFLAGS) $(ldflags-$(2)) -shared -o $(2) $(1) - -# print line used to shlib object files -ifeq ($(V),1) -shlib_print_cmd = echo $(call protect_quote,$(call shlib_cmd,$1,$2)) -else -shlib_print_cmd = echo " SHLIB $(2)" -endif - -all-clean-file += $(all-shlib) diff --git a/mk/ecoli-slink-rules.mk b/mk/ecoli-slink-rules.mk deleted file mode 100644 index f91a09a..0000000 --- a/mk/ecoli-slink-rules.mk +++ /dev/null @@ -1,31 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# dump some infos if debug is enabled -ifeq ($(D),1) -$(call disp_list,------ all-slink,$(all-slink)) -$(foreach slink,$(all-slink),\ - $(info,out-$(slink): $(out-$(slink))) \ - $(call disp_list,pre-$(slink),$(pre-$(slink))) \ -) -endif - -# include dependencies and commands files if they exist -$(foreach slink,$(all-slink),\ - $(eval -include $(call depfile,$(slink))) \ - $(eval -include $(call cmdfile,$(slink))) \ -) - -# remove duplicates -filtered-all-slink := $(sort $(all-slink)) - -# convert format of executable -$(filtered-all-slink): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE - @[ -d $(dir $@) ] || mkdir -p $(dir $@) - @$(call display_deps,$(pre-$(@)),$@,\ - $(call slink_cmd,$(pre-$(@)),$@),$?) - @$(if $(call check_deps,$@,$(call slink_cmd,$(pre-$(@)),$@),$?),\ - $(call slink_print_cmd,$(pre-$(@)),$@) && \ - $(call slink_cmd,$(pre-$(@)),$@) && \ - $(call save_cmd,$(call slink_cmd,$(pre-$(@)),$@),$@) && \ - $(call create_empty_depfile,$@)) diff --git a/mk/ecoli-slink-vars.mk b/mk/ecoli-slink-vars.mk deleted file mode 100644 index 1559a98..0000000 --- a/mk/ecoli-slink-vars.mk +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# create a symbolic link of a file -# slink-y-$(slink) is provided by the user -# $(slink) is the path of the directory containing the destination -# files, and the variable contains the path of the files to linked. Several -# slink-y-$(slink) can be present. - -# list all path requested by user -_all-slink := $(patsubst slink-y-%,%,$(filter slink-y-%,$(.VARIABLES))) -all-slink := - -# for each slink, create the following variables: -# out-$(slink) = output path of the executable -# pre-$(slink) = list of prerequisites for this executable -# We also add the files in $(all-slink). -$(foreach slink,$(_all-slink),\ - $(if $(notdir $(slink)), \ - $(if $(call compare,$(words $(slink-y-$(slink))),1), \ - $(error "only one source file is allowed in slink-y-$(slink)")) \ - $(eval dst := $(dir $(slink))$(notdir $(slink-y-$(slink)))) \ - $(eval out-$(slink) := $(dir $(slink))) \ - $(eval pre-$(slink) := $(slink-y-$(slink))) \ - $(eval all-slink += $(dst)) \ - , \ - $(foreach src,$(slink-y-$(slink)),\ - $(eval dst := $(slink)$(notdir $(src))) \ - $(eval out-$(slink) := $(slink)) \ - $(eval pre-$(dst) := $(src)) \ - $(eval all-slink += $(dst)) \ - ) \ - ) \ -) - -# add them to the list of targets and clean -all-targets += $(all-slink) -all-clean-file += $(all-slink) - -# convert format of executable from elf to ihex -# $1: source executable (elf) -# $2: destination file -slink_cmd = $(LN) -nsf $(abspath $(1)) $(2) - -# print line used to convert executable format -ifeq ($(V),1) -slink_print_cmd = echo $(call protect_quote,$(call slink_cmd,$1,$2)) -else -slink_print_cmd = echo " SLINK $(2)" -endif diff --git a/mk/ecoli-subdir-rules.mk b/mk/ecoli-subdir-rules.mk deleted file mode 100644 index c80166b..0000000 --- a/mk/ecoli-subdir-rules.mk +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -.PHONY: $(subdir-y) -$(subdir-y): FORCE - $(Q)$(MAKE) -C $(@) $(mkflags-$(@)) $(MAKECMDGOALS) diff --git a/mk/ecoli-subdir-vars.mk b/mk/ecoli-subdir-vars.mk deleted file mode 100644 index 4190595..0000000 --- a/mk/ecoli-subdir-vars.mk +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# subdir-y is provided by the user -# it contains the list of directory to build - -# add them to the list of targets -all-targets += $(subdir-y) -all-clean-target += $(subdir-y) diff --git a/mk/ecoli-tools.mk b/mk/ecoli-tools.mk deleted file mode 100644 index 93be008..0000000 --- a/mk/ecoli-tools.mk +++ /dev/null @@ -1,135 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -empty:= -space:= $(empty) $(empty) -indent:= $(space)$(space) - -# define a newline char, useful for debugging with $(info) -define newline - - -endef - -# $(prefix shell commands with $(Q) to silent them, except if V=1 -Q=@ -ifeq ("$(V)-$(origin V)", "1-command line") -Q= -endif - -# set variable $1 to $2 if the variable has an implicit value or -# is not defined -# $1 variable name -# $2 new variable content -set_default = $(if \ - $(call not,$(or \ - $(compare $(origin $(1)),default), \ - $(compare $(origin $(1)),undefined) \ - )),\ - $(eval $(1) = $(2)) \ -) - -# display a list -# $1 title -# $2 list -disp_list = $(info $(1)$(newline)\ - $(addsuffix $(newline),$(addprefix $(space),$(2)))) - -# add a dot in front of the file name -# $1 list of paths -# return: full paths with files prefixed by a dot -dotfile = $(strip $(foreach f,$(1),\ - $(join $(dir $f),.$(notdir $f)))) - -# convert source/obj files into dot-dep filename -# $1 list of paths -# return: full paths with files prefixed by a dot and suffixed with .d -depfile = $(strip $(call dotfile,$(addsuffix .d,$(1)))) - -# convert source/obj files into dot-dep filename -# $1 list of paths -# return: full paths with files prefixed by a dot and suffixed with .d.tmp -file2tmpdep = $(strip $(call dotfile,$(addsuffix .d.tmp,$(1)))) - -# convert source/obj files into dot-cmd filename -# $1 list of paths -# return: full paths with files prefixed by a dot and suffixed with .cmd -cmdfile = $(strip $(call dotfile,$(addsuffix .cmd,$(1)))) - -# add a \ before each quote -protect_quote = $(subst ','\'',$(1)) -#'# editor syntax highlight fix - -# return an non-empty string if $1 is empty, and vice versa -# $1 a string -not = $(if $1,,true) - -# return 1 if parameter is a non-empty string, else 0 -boolean = $(if $1,1,0) - -# return an empty string if string are equal -compare = $(strip $(subst $(1),,$(2)) $(subst $(2),,$(1))) - -# return a non-empty string if a file does not exist -# $1: file -file_missing = $(call compare,$(wildcard $1),$1) - -# return a non-empty string if cmdline changed -# $1: file to be built -# $2: the command to build it -cmdline_changed = $(call compare,$(strip $(cmd-$(1))),$(strip $(2))) - -# return an non-empty string if the .d file does not exist -# $1: the dep file (.d) -depfile_missing = $(call compare,$(wildcard $(1)),$(1)) - -# return a non-empty string if, according to dep-xyz variable, a file -# needed to build $1 does not exist. In this case we need to rebuild -# the file and the .d file. -# $1: file to be built -dep-missing = $(call compare,$(wildcard $(dep-$(1))),$(dep-$(1))) - -# return an empty string if no prereq is newer than target -# $1: list of prerequisites newer than target ($?) -dep-newer = $(strip $(filter-out FORCE,$(1))) - -# display why a file should be re-built -# $1: source files -# $2: dst file -# $3: build command -# $4: all prerequisites newer than target ($?) -ifeq ($(D),1) -display_deps = \ - echo -n "$1 -> $2 " ; \ - echo -n "file_missing=$(call boolean,$(call file_missing,$(2))) " ; \ - echo -n "cmdline_changed=$(call boolean,$(call cmdline_changed,$(2),$(3))) " ; \ - echo -n "depfile_missing=$(call boolean,$(call depfile_missing,$(call depfile,$(2)))) " ; \ - echo -n "dep-missing=$(call boolean,$(call dep-missing,$(2))) " ; \ - echo "dep-newer=$(call boolean,$(call dep-newer,$(4)))" -else -display_deps= -endif - -# return an empty string if a file should be rebuilt -# $1: dst file -# $2: build command -# $3: all prerequisites newer than target ($?) -check_deps = \ - $(or $(call file_missing,$(1)),\ - $(call cmdline_changed,$(1),$(2)),\ - $(call depfile_missing,$(call depfile,$(1))),\ - $(call dep-missing,$(1)),\ - $(call dep-newer,$(3))) - -# create a depfile (.d) with no additional deps -# $1: object file (.o) -create_empty_depfile = echo "dep-$(1) =" > $(call depfile,$(1)) - -# save a command in a file -# $1: command to build the file -# $2: name of the file -save_cmd = echo "cmd-$(2) = $(call protect_quote,$(1))" > $(call cmdfile,$(2)) - -# remove the FORCE target from the list of all prerequisites $+ -# no arguments, use $+ -prereq = $(filter-out FORCE,$(+)) diff --git a/mk/ecoli-vars.mk b/mk/ecoli-vars.mk deleted file mode 100644 index 7c69466..0000000 --- a/mk/ecoli-vars.mk +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2015, Olivier MATZ - -# core tools -CP ?= cp -LN ?= ln -GAWK ?= gawk -GREP ?= grep -# compiler and binutils, set_default overrides mk implicit value -# but not command line or standard variables -$(call set_default,CC,$(CROSS)gcc) -$(call set_default,CPP,$(CROSS)cpp) -$(call set_default,AR,$(CROSS)ar) -$(call set_default,LD,$(CROSS)ld) -$(call set_default,OBJCOPY,$(CROSS)objcopy) -$(call set_default,OBJDUMP,$(CROSS)objdump) -$(call set_default,STRIP,$(CROSS)strip) -HOSTCC ?= cc - -CFLAGS += $(EXTRA_CFLAGS) -CPPFLAGS += $(EXTRA_CPPFLAGS) -LDFLAGS += $(EXTRA_LDFLAGS) -LDLIBS += $(EXTRA_LDLIBS) diff --git a/src/ecoli_assert.c b/src/ecoli_assert.c new file mode 100644 index 0000000..a36d6f6 --- /dev/null +++ b/src/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/src/ecoli_complete.c b/src/ecoli_complete.c new file mode 100644 index 0000000..a9becdf --- /dev/null +++ b/src/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(const 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) +{ + const 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/src/ecoli_config.c b/src/ecoli_config.c new file mode 100644 index 0000000..66d9232 --- /dev/null +++ b/src/ecoli_config.c @@ -0,0 +1,1172 @@ +/* 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; +} + +ssize_t ec_config_count(const struct ec_config *config) +{ + const struct ec_config *child; + ssize_t n; + + switch (config->type) { + case EC_CONFIG_TYPE_LIST: + n = 0; + TAILQ_FOREACH(child, &config->list, next) + n++; + return n; + case EC_CONFIG_TYPE_DICT: + // XXX todo + default: + errno = EINVAL; + return -1; + } +} + +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/src/ecoli_editline.c b/src/ecoli_editline.c new file mode 100644 index 0000000..4cc7ec4 --- /dev/null +++ b/src/ecoli_editline.c @@ -0,0 +1,684 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct ec_editline { + EditLine *el; + History *history; + HistEvent histev; + const struct ec_node *node; + char *prompt; +}; + +/* used by qsort below */ +static int +strcasecmp_cb(const void *p1, const void *p2) +{ + return strcasecmp(*(char * const *)p1, *(char * const *)p2); +} + +/* Show the matches as a multi-columns list */ +int +ec_editline_print_cols(struct ec_editline *editline, + char const * const *matches, size_t n) +{ + size_t max_strlen = 0, len, i, j, ncols; + int width, height; + const char *space; + char **matches_copy = NULL; + FILE *f; + + if (el_get(editline->el, EL_GETFP, 1, &f)) + return -1; + + fprintf(f, "\n"); + if (n == 0) + return 0; + + if (el_get(editline->el, EL_GETTC, "li", &height, (void *)0)) + return -1; + if (el_get(editline->el, EL_GETTC, "co", &width, (void *)0)) + return -1; + + /* duplicate the matches table, and sort it */ + matches_copy = calloc(n, sizeof(const char *)); + if (matches_copy == NULL) + return -1; + memcpy(matches_copy, matches, sizeof(const char *) * n); + qsort(matches_copy, n, sizeof(char *), strcasecmp_cb); + + /* get max string length */ + for (i = 0; i < n; i++) { + len = strlen(matches_copy[i]); + if (len > max_strlen) + max_strlen = len; + } + + /* write the columns */ + ncols = width / (max_strlen + 4); + if (ncols == 0) + ncols = 1; + for (i = 0; i < n; i+= ncols) { + for (j = 0; j < ncols; j++) { + if (i + j >= n) + break; + if (j == 0) + space = ""; + else + space = " "; + fprintf(f, "%s%-*s", space, + (int)max_strlen, matches[i+j]); + } + fprintf(f, "\n"); + } + + free(matches_copy); + return 0; +} + +/* Show the helps on editline output */ +int +ec_editline_print_helps(struct ec_editline *editline, + const struct ec_editline_help *helps, size_t len) +{ + size_t i; + FILE *out; + + if (el_get(editline->el, EL_GETFP, 1, &out)) + return -1; + + for (i = 0; i < len; i++) { + if (fprintf(out, "%-20s %s\n", + helps[i].desc, helps[i].help) < 0) + return -1; + } + + return 0; +} + +void +ec_editline_free_helps(struct ec_editline_help *helps, size_t len) +{ + size_t i; + + if (helps == NULL) + return; + for (i = 0; i < len; i++) { + ec_free(helps[i].desc); + ec_free(helps[i].help); + } + ec_free(helps); +} + +int +ec_editline_set_prompt(struct ec_editline *editline, const char *prompt) +{ + char *copy = NULL; + + if (prompt != NULL) { + ec_strdup(prompt); + if (copy == NULL) + return -1; + } + + ec_free(editline->prompt); + editline->prompt = copy; + + return 0; +} + +static char * +prompt_cb(EditLine *el) +{ + struct ec_editline *editline; + void *clientdata; + + if (el_get(el, EL_CLIENTDATA, &clientdata)) + return "> "; + editline = clientdata; + + if (editline == NULL) + return "> "; + + return editline->prompt; +} + +struct ec_editline * +ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err, + unsigned int flags) +{ + struct ec_editline *editline = NULL; + EditLine *el; + + if (f_in == NULL || f_out == NULL || f_err == NULL) { + errno = EINVAL; + goto fail; + } + + editline = ec_calloc(1, sizeof(*editline)); + if (editline == NULL) + goto fail; + + el = el_init(name, f_in, f_out, f_err); + if (el == NULL) + goto fail; + editline->el = el; + + /* save editline pointer as user data */ + if (el_set(el, EL_CLIENTDATA, editline)) + goto fail; + + /* install default editline signals */ + if (el_set(el, EL_SIGNAL, 1)) + goto fail; + + if (el_set(el, EL_PREP_TERM, 0)) + goto fail; + + /* use emacs bindings */ + if (el_set(el, EL_EDITOR, "emacs")) + goto fail; + if (el_set(el, EL_BIND, "^W", "ed-delete-prev-word", NULL)) + goto fail; + + /* ask terminal to not send signals */ + if (flags & EC_EDITLINE_DISABLE_SIGNALS) { + if (el_set(el, EL_SETTY, "-d", "-isig", NULL)) + goto fail; + } + + /* set prompt */ + editline->prompt = ec_strdup("> "); + if (editline->prompt == NULL) + goto fail; + if (el_set(el, EL_PROMPT, prompt_cb)) + goto fail; + + /* set up history */ + if ((flags & EC_EDITLINE_DISABLE_HISTORY) == 0) { + if (ec_editline_set_history( + editline, EC_EDITLINE_HISTORY_SIZE) < 0) + goto fail; + } + + /* register completion callback */ + if ((flags & EC_EDITLINE_DISABLE_COMPLETION) == 0) { + if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer", + ec_editline_complete)) + goto fail; + if (el_set(el, EL_BIND, "^I", "ed-complete", NULL)) + goto fail; + if (el_set(el, EL_BIND, "?", "ed-complete", NULL)) + goto fail; + } + + return editline; + +fail: + ec_editline_free(editline); + return NULL; +} + +void ec_editline_free(struct ec_editline *editline) +{ + if (editline == NULL) + return; + if (editline->el != NULL) + el_end(editline->el); + if (editline->history != NULL) + history_end(editline->history); + ec_free(editline->prompt); + ec_free(editline); +} + +EditLine *ec_editline_get_el(struct ec_editline *editline) +{ + return editline->el; +} + +const struct ec_node * +ec_editline_get_node(struct ec_editline *editline) +{ + return editline->node; +} + +void +ec_editline_set_node(struct ec_editline *editline, const struct ec_node *node) +{ + editline->node = node; +} + +int ec_editline_set_history(struct ec_editline *editline, + size_t hist_size) +{ + EditLine *el = editline->el; + + if (editline->history != NULL) + history_end(editline->history); + + if (hist_size == 0) + return 0; + + editline->history = history_init(); + if (editline->history == NULL) + goto fail; + if (history(editline->history, &editline->histev, H_SETSIZE, + hist_size) < 0) + goto fail; + if (history(editline->history, &editline->histev, H_SETUNIQUE, 1)) + goto fail; + if (el_set(el, EL_HIST, history, editline->history)) + goto fail; + + return 0; + +fail: + //XXX errno + if (editline->history != NULL) { + history_end(editline->history); + editline->history = NULL; + } + return -1; +} + +void ec_editline_free_completions(char **matches, size_t len) +{ + size_t i; + + // XXX use ec_malloc/ec_free() instead for consistency + if (matches == NULL) + return; + for (i = 0; i < len; i++) + free(matches[i]); + free(matches); +} + +ssize_t +ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out) +{ + const struct ec_comp_item *item; + struct ec_comp_iter *iter = NULL; + char **matches = NULL; + size_t count = 0; + + iter = ec_comp_iter(cmpl, EC_COMP_FULL | EC_COMP_PARTIAL); + if (iter == NULL) + goto fail; + + while ((item = ec_comp_iter_next(iter)) != NULL) { + char **tmp; + + tmp = realloc(matches, (count + 1) * sizeof(char *)); + if (tmp == NULL) + goto fail; + matches = tmp; + matches[count] = strdup(ec_comp_item_get_display(item)); + if (matches[count] == NULL) + goto fail; + count++; + } + + *matches_out = matches; + return count; + +fail: + ec_editline_free_completions(matches, count); + *matches_out = NULL; + ec_comp_iter_free(iter); + return -1; +} + +char * +ec_editline_append_chars(const struct ec_comp *cmpl) +{ + const struct ec_comp_item *item; + struct ec_comp_iter *iter = NULL; + const char *append; + char *ret = NULL; + size_t n; + + iter = ec_comp_iter(cmpl, EC_COMP_FULL | EC_COMP_PARTIAL); + if (iter == NULL) + goto fail; + + while ((item = ec_comp_iter_next(iter)) != NULL) { + append = ec_comp_item_get_completion(item); + if (ret == NULL) { + ret = ec_strdup(append); + if (ret == NULL) + goto fail; + } else { + n = ec_strcmp_count(ret, append); + ret[n] = '\0'; + } + } + ec_comp_iter_free(iter); + + return ret; + +fail: + ec_comp_iter_free(iter); + ec_free(ret); + + return NULL; +} + +/* this function builds the help string */ +static int get_node_help(const struct ec_comp_item *item, + struct ec_editline_help *help) +{ + const struct ec_comp_group *grp; + const struct ec_parse *state; + const struct ec_node *node; + const char *node_help = NULL; + const char *node_desc = NULL; + + help->desc = NULL; + help->help = NULL; + + grp = ec_comp_item_get_grp(item); + + 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) + goto fail; + + help->desc = ec_strdup(node_desc); + if (help->desc == NULL) + goto fail; + + help->help = ec_strdup(node_help); + if (help->help == NULL) + goto fail; + + return 0; + +fail: + ec_free(help->desc); + ec_free(help->help); + return -1; +} + +ssize_t +ec_editline_get_helps(const struct ec_editline *editline, const char *line, + const char *full_line, struct ec_editline_help **helps_out) +{ + struct ec_comp_iter *iter = NULL; + const struct ec_comp_group *grp, *prev_grp = NULL; + const struct ec_comp_item *item; + struct ec_comp *cmpl = NULL; + struct ec_parse *parse = NULL; + unsigned int count = 0; + struct ec_editline_help *helps = NULL; + + *helps_out = NULL; + + /* check if the current line matches */ + parse = ec_node_parse(editline->node, full_line); + if (ec_parse_matches(parse)) + count = 1; + ec_parse_free(parse); + parse = NULL; + + /* complete at current cursor position */ + cmpl = ec_node_complete(editline->node, line); + if (cmpl == NULL) //XXX log error + goto fail; + + /* let's display one contextual help per node */ + iter = ec_comp_iter(cmpl, + EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL); + if (iter == NULL) + goto fail; + + helps = ec_calloc(1, sizeof(*helps)); + if (helps == NULL) + goto fail; + if (count == 1) { + helps[0].desc = ec_strdup(""); + if (helps[0].desc == NULL) + goto fail; + helps[0].help = ec_strdup("Validate command."); + if (helps[0].help == NULL) + goto fail; + } + + while ((item = ec_comp_iter_next(iter)) != NULL) { + struct ec_editline_help *tmp = NULL; + + /* keep one help per group, skip other items */ + grp = ec_comp_item_get_grp(item); + if (grp == prev_grp) + continue; + + prev_grp = grp; + + tmp = ec_realloc(helps, (count + 1) * sizeof(*helps)); + if (tmp == NULL) + goto fail; + helps = tmp; + if (get_node_help(item, &helps[count]) < 0) + goto fail; + count++; + } + + ec_comp_iter_free(iter); + ec_comp_free(cmpl); + *helps_out = helps; + + return count; + +fail: + ec_comp_iter_free(iter); + ec_parse_free(parse); + ec_comp_free(cmpl); + if (helps != NULL) { + while (count--) { + ec_free(helps[count].desc); + ec_free(helps[count].help); + } + ec_free(helps); + } + + return -1; +} + +int +ec_editline_complete(EditLine *el, int c) +{ + struct ec_editline *editline; + const LineInfo *line_info; + int ret = CC_REFRESH; + struct ec_comp *cmpl = NULL; + char *append = NULL; + char *line = NULL; + void *clientdata; + FILE *out, *err; + int len; + + if (el_get(el, EL_GETFP, 1, &out)) + return -1; + if (el_get(el, EL_GETFP, 1, &err)) + return -1; + + (void)c; + + if (el_get(el, EL_CLIENTDATA, &clientdata)) { + fprintf(err, "completion failure: no client data\n"); + goto fail; + } + editline = clientdata; + (void)editline; + + line_info = el_line(el); + if (line_info == NULL) { + fprintf(err, "completion failure: no line info\n"); + goto fail; + } + + len = line_info->cursor - line_info->buffer; + if (ec_asprintf(&line, "%.*s", len, line_info->buffer) < 0) { + fprintf(err, "completion failure: no memory\n"); + goto fail; + } + + if (editline->node == NULL) { + fprintf(err, "completion failure: no ec_node\n"); + goto fail; + } + + cmpl = ec_node_complete(editline->node, line); + if (cmpl == NULL) + goto fail; + + append = ec_editline_append_chars(cmpl); + + if (c == '?') { + struct ec_editline_help *helps = NULL; + ssize_t count = 0; + + count = ec_editline_get_helps(editline, line, line_info->buffer, + &helps); + + fprintf(out, "\n"); + if (ec_editline_print_helps(editline, helps, count) < 0) { + fprintf(err, "completion failure: cannot show help\n"); + ec_editline_free_helps(helps, count); + goto fail; + } + + ec_editline_free_helps(helps, count); + ret = CC_REDISPLAY; + } else if (append == NULL || strcmp(append, "") == 0) { + char **matches = NULL; + ssize_t count = 0; + + count = ec_editline_get_completions(cmpl, &matches); + if (count < 0) { + fprintf(err, "completion failure: cannot get completions\n"); + goto fail; + } + + if (ec_editline_print_cols( + editline, + EC_CAST(matches, char **, + char const * const *), + count) < 0) { + fprintf(err, "completion failure: cannot print\n"); + ec_editline_free_completions(matches, count); + goto fail; + } + + ec_editline_free_completions(matches, count); + ret = CC_REDISPLAY; + } else { + if (el_insertstr(el, append) < 0) { + fprintf(err, "completion failure: cannot insert\n"); + goto fail; + } + if (ec_comp_count(cmpl, EC_COMP_FULL) + + ec_comp_count(cmpl, EC_COMP_PARTIAL) == 1) { + if (el_insertstr(el, " ") < 0) { + fprintf(err, "completion failure: cannot insert space\n"); + goto fail; + } + } + } + + ec_comp_free(cmpl); + ec_free(line); + ec_free(append); + + return ret; + +fail: + ec_comp_free(cmpl); + ec_free(line); + ec_free(append); + + return CC_ERROR; +} + +char * +ec_editline_gets(struct ec_editline *editline) +{ + EditLine *el = editline->el; + char *line_copy = NULL; + const char *line; + int count; + + line = el_gets(el, &count); + if (line == NULL) + return NULL; + + line_copy = ec_strdup(line); + if (line_copy == NULL) + goto fail; + + line_copy[strlen(line_copy) - 1] = '\0'; //XXX needed because of sh_lex bug? + + if (editline->history != NULL && !ec_str_is_space(line_copy)) { + history(editline->history, &editline->histev, + H_ENTER, line_copy); + } + + return line_copy; + +fail: + ec_free(line_copy); + return NULL; +} + +struct ec_parse * +ec_editline_parse(struct ec_editline *editline, const struct ec_node *node) +{ + char *line = NULL; + struct ec_parse *parse = NULL; + + /* XXX add sh_lex automatically? This node is required, parse and + * complete are based on it. */ + + ec_editline_set_node(editline, node); + + line = ec_editline_gets(editline); + if (line == NULL) + goto fail; + + parse = ec_node_parse(node, line); + if (parse == NULL) + goto fail; + + ec_free(line); + return parse; + +fail: + ec_free(line); + ec_parse_free(parse); + + return NULL; +} + diff --git a/src/ecoli_init.c b/src/ecoli_init.c new file mode 100644 index 0000000..fd5c0c3 --- /dev/null +++ b/src/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/src/ecoli_keyval.c b/src/ecoli_keyval.c new file mode 100644 index 0000000..12fe66b --- /dev/null +++ b/src/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/src/ecoli_log.c b/src/ecoli_log.c new file mode 100644 index 0000000..aefba83 --- /dev/null +++ b/src/ecoli_log.c @@ -0,0 +1,229 @@ +/* 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; + + // XXX not that good to allocate in constructor + 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/src/ecoli_malloc.c b/src/ecoli_malloc.c new file mode 100644 index 0000000..5a022ae --- /dev/null +++ b/src/ecoli_malloc.c @@ -0,0 +1,179 @@ +/* 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); +} + +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); +} + +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); +} + +void ec_realloc_func(void *ptr, size_t size) +{ + ec_realloc(ptr, size); +} + +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/src/ecoli_murmurhash.c b/src/ecoli_murmurhash.c new file mode 100644 index 0000000..7aafece --- /dev/null +++ b/src/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/src/ecoli_node.c b/src/ecoli_node.c new file mode 100644 index 0000000..9789102 --- /dev/null +++ b/src/ecoli_node.c @@ -0,0 +1,608 @@ +/* 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; + + // XXX check that id matches [_a-zA-Z][:-_0-9a-zA-Z]* + 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/src/ecoli_node_any.c b/src/ecoli_node_any.c new file mode 100644 index 0000000..9166dbb --- /dev/null +++ b/src/ecoli_node_any.c @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#include +#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; + char *attr_name; +}; + +static int ec_node_any_parse(const struct ec_node *gen_node, + struct ec_parse *state, + const struct ec_strvec *strvec) +{ + struct ec_node_any *node = (struct ec_node_any *)gen_node; + const struct ec_keyval *attrs; + + (void)state; + + if (ec_strvec_len(strvec) == 0) + return EC_PARSE_NOMATCH; + if (node->attr_name != NULL) { + attrs = ec_strvec_get_attrs(strvec, 0); + if (attrs == NULL || !ec_keyval_has_key(attrs, node->attr_name)) + return EC_PARSE_NOMATCH; + } + + return 1; +} + +static void ec_node_any_free_priv(struct ec_node *gen_node) +{ + struct ec_node_any *node = (struct ec_node_any *)gen_node; + + ec_free(node->attr_name); +} + +static const struct ec_config_schema ec_node_any_schema[] = { + { + .key = "attr", + .desc = "The optional attribute name to attach.", + .type = EC_CONFIG_TYPE_STRING, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_any_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_any *node = (struct ec_node_any *)gen_node; + const struct ec_config *value = NULL; + char *s = NULL; + + value = ec_config_dict_get(config, "attr"); + if (value != NULL) { + s = ec_strdup(value->string); + if (s == NULL) + goto fail; + } + + ec_free(node->attr_name); + node->attr_name = s; + + return 0; + +fail: + ec_free(s); + return -1; +} + +static struct ec_node_type ec_node_any_type = { + .name = "any", + .schema = ec_node_any_schema, + .set_config = ec_node_any_set_config, + .parse = ec_node_any_parse, + .complete = ec_node_complete_unknown, + .size = sizeof(struct ec_node_any), + .free_priv = ec_node_any_free_priv, +}; + +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/src/ecoli_node_cmd.c b/src/ecoli_node_cmd.c new file mode 100644 index 0000000..3b56084 --- /dev/null +++ b/src/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, NULL); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "[*|,()]", 1, NULL); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "\\[", 1, NULL); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "\\]", 1, NULL); + if (ret < 0) + goto fail; + ret = ec_node_re_lex_add(lex, "[ ]+", 0, NULL); + if (ret < 0) + goto fail; + + return lex; + +fail: + ec_node_free(lex); + + 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/src/ecoli_node_dynamic.c b/src/ecoli_node_dynamic.c new file mode 100644 index 0000000..8a3edf3 --- /dev/null +++ b/src/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/src/ecoli_node_empty.c b/src/ecoli_node_empty.c new file mode 100644 index 0000000..6ce76e8 --- /dev/null +++ b/src/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/src/ecoli_node_expr.c b/src/ecoli_node_expr.c new file mode 100644 index 0000000..3b47d8c --- /dev/null +++ b/src/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/src/ecoli_node_expr_test.c b/src/ecoli_node_expr_test.c new file mode 100644 index 0000000..e3a0c79 --- /dev/null +++ b/src/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, NULL); /* vars */ + testres |= ec_node_re_lex_add(lex_node, "[+*!^()]", 1, NULL); /* operators */ + testres |= ec_node_re_lex_add(lex_node, "[ ]+", 0, NULL); /* 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/src/ecoli_node_file.c b/src/ecoli_node_file.c new file mode 100644 index 0000000..001dcb6 --- /dev/null +++ b/src/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/src/ecoli_node_helper.c b/src/ecoli_node_helper.c new file mode 100644 index 0000000..94811f2 --- /dev/null +++ b/src/ecoli_node_helper.c @@ -0,0 +1,94 @@ +/* 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; + ssize_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 = ec_config_count(config); + if (n < 0) + return NULL; + + table = ec_calloc(n, sizeof(*table)); + if (table == NULL) + goto fail; + + n = 0; + TAILQ_FOREACH(child, &config->list, next) { + if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { + errno = EINVAL; + goto fail; + } + 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/src/ecoli_node_int.c b/src/ecoli_node_int.c new file mode 100644 index 0000000..9b56e22 --- /dev/null +++ b/src/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/src/ecoli_node_many.c b/src/ecoli_node_many.c new file mode 100644 index 0000000..dfdd866 --- /dev/null +++ b/src/ecoli_node_many.c @@ -0,0 +1,416 @@ +/* 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_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 = 2; + return 0; +} + +static const struct ec_config_schema ec_node_many_schema[] = { + { + .key = "child", + .desc = "The child node.", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .key = "min", + .desc = "The minimum number of matches (default = 0).", + .type = EC_CONFIG_TYPE_UINT64, + }, + { + .key = "max", + .desc = "The maximum number of matches. If 0, there is " + "no maximum (default = 0).", + .type = EC_CONFIG_TYPE_UINT64, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_many_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_many *node = (struct ec_node_many *)gen_node; + const struct ec_config *child, *min, *max; + + child = ec_config_dict_get(config, "child"); + if (child == NULL) + goto fail; + if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { + errno = EINVAL; + goto fail; + } + min = ec_config_dict_get(config, "min"); + if (min != NULL && (ec_config_get_type(min) != EC_CONFIG_TYPE_UINT64 || + min->u64 >= UINT_MAX)) { + errno = EINVAL; + goto fail; + } + max = ec_config_dict_get(config, "max"); + if (max != NULL && (ec_config_get_type(max) != EC_CONFIG_TYPE_UINT64 || + max->u64 >= UINT_MAX)) { + errno = EINVAL; + goto fail; + } + + if (node->child != NULL) + ec_node_free(node->child); + node->child = ec_node_clone(child->node); + if (min == NULL) + node->min = 0; + else + node->min = min->u64; + if (max == NULL) + node->max = 0; + else + node->max = max->u64; + + return 0; + +fail: + return -1; +} + +static struct ec_node_type ec_node_many_type = { + .name = "many", + .schema = ec_node_many_schema, + .set_config = ec_node_many_set_config, + .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); + +int +ec_node_many_set_params(struct ec_node *gen_node, struct ec_node *child, + unsigned int min, unsigned int max) +{ + const struct ec_config *cur_config = NULL; + struct ec_config *config = NULL; + int ret; + + if (ec_node_check_type(gen_node, &ec_node_many_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; + + if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { + child = NULL; /* freed */ + goto fail; + } + child = NULL; /* freed */ + + if (ec_config_dict_set(config, "min", ec_config_u64(min)) < 0) + goto fail; + if (ec_config_dict_set(config, "max", ec_config_u64(max)) < 0) + 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_many(const char *id, struct ec_node *child, + unsigned int min, unsigned int max) +{ + struct ec_node *gen_node = NULL; + + if (child == NULL) + return NULL; + + gen_node = ec_node_from_type(&ec_node_many_type, id); + if (gen_node == NULL) + goto fail; + + if (ec_node_many_set_params(gen_node, child, min, max) < 0) { + child = NULL; + goto fail; + } + child = NULL; + + return gen_node; + +fail: + ec_node_free(child); + return NULL; +} + +/* 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/src/ecoli_node_none.c b/src/ecoli_node_none.c new file mode 100644 index 0000000..dba9ebf --- /dev/null +++ b/src/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/src/ecoli_node_once.c b/src/ecoli_node_once.c new file mode 100644 index 0000000..989c710 --- /dev/null +++ b/src/ecoli_node_once.c @@ -0,0 +1,282 @@ +/* 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_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 = 2; + return 0; +} + +static const struct ec_config_schema ec_node_once_schema[] = { + { + .key = "child", + .desc = "The child node.", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_once_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_once *node = (struct ec_node_once *)gen_node; + const struct ec_config *child; + + child = ec_config_dict_get(config, "child"); + if (child == NULL) + goto fail; + if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { + errno = EINVAL; + goto fail; + } + + if (node->child != NULL) + ec_node_free(node->child); + node->child = ec_node_clone(child->node); + + return 0; + +fail: + return -1; +} + +static struct ec_node_type ec_node_once_type = { + .name = "once", + .schema = ec_node_once_schema, + .set_config = ec_node_once_set_config, + .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_child(struct ec_node *gen_node, struct ec_node *child) +{ + const struct ec_config *cur_config = NULL; + struct ec_config *config = NULL; + int ret; + + if (ec_node_check_type(gen_node, &ec_node_once_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; + + if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { + child = NULL; /* freed */ + goto fail; + } + child = NULL; /* freed */ + + 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_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_child(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/src/ecoli_node_option.c b/src/ecoli_node_option.c new file mode 100644 index 0000000..4b26001 --- /dev/null +++ b/src/ecoli_node_option.c @@ -0,0 +1,239 @@ +/* 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_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 = 2; + return 0; +} + +static const struct ec_config_schema ec_node_option_schema[] = { + { + .key = "child", + .desc = "The child node.", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_option_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_option *node = (struct ec_node_option *)gen_node; + const struct ec_config *child; + + child = ec_config_dict_get(config, "child"); + if (child == NULL) + goto fail; + if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { + errno = EINVAL; + goto fail; + } + + if (node->child != NULL) + ec_node_free(node->child); + node->child = ec_node_clone(child->node); + + return 0; + +fail: + return -1; +} + +static struct ec_node_type ec_node_option_type = { + .name = "option", + .schema = ec_node_option_schema, + .set_config = ec_node_option_set_config, + .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_child(struct ec_node *gen_node, struct ec_node *child) +{ + const struct ec_config *cur_config = NULL; + struct ec_config *config = NULL; + int ret; + + if (ec_node_check_type(gen_node, &ec_node_option_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; + + if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { + child = NULL; /* freed */ + goto fail; + } + child = NULL; /* freed */ + + 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_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_child(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/src/ecoli_node_or.c b/src/ecoli_node_or.c new file mode 100644 index 0000000..2bf8fc7 --- /dev/null +++ b/src/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/src/ecoli_node_re.c b/src/ecoli_node_re.c new file mode 100644 index 0000000..69f352c --- /dev/null +++ b/src/ecoli_node_re.c @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#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 const struct ec_config_schema ec_node_re_schema[] = { + { + .key = "pattern", + .desc = "The pattern to match.", + .type = EC_CONFIG_TYPE_STRING, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_re_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_re *node = (struct ec_node_re *)gen_node; + const struct ec_config *value = NULL; + char *s = NULL; + regex_t re; + int ret; + + value = ec_config_dict_get(config, "pattern"); + if (value == NULL) { + errno = EINVAL; + goto fail; + } + + s = ec_strdup(value->string); + if (s == NULL) + goto fail; + + ret = regcomp(&re, s, 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 = s; + node->re = re; + + return 0; + +fail: + ec_free(s); + return -1; +} + +static struct ec_node_type ec_node_re_type = { + .name = "re", + .schema = ec_node_re_schema, + .set_config = ec_node_re_set_config, + .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_config *config = NULL; + int ret; + + EC_CHECK_ARG(str != NULL, -1, EINVAL); + + if (ec_node_check_type(gen_node, &ec_node_re_type) < 0) + goto fail; + + config = ec_config_dict(); + if (config == NULL) + goto fail; + + ret = ec_config_dict_set(config, "pattern", 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_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/src/ecoli_node_re_lex.c b/src/ecoli_node_re_lex.c new file mode 100644 index 0000000..4ebb9fd --- /dev/null +++ b/src/ecoli_node_re_lex.c @@ -0,0 +1,564 @@ +/* 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 + +EC_LOG_TYPE_REGISTER(node_re_lex); + +struct regexp_pattern { + char *pattern; + char *attr_name; + 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; + struct ec_keyval *attrs = 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; + + if (table[i].attr_name != NULL) { + attrs = ec_keyval(); + if (attrs == NULL) + goto fail; + if (ec_keyval_set(attrs, table[i].attr_name, + NULL, NULL) < 0) + goto fail; + if (ec_strvec_set_attrs(strvec, + ec_strvec_len(strvec) - 1, + attrs) < 0) { + attrs = NULL; + goto fail; + } + attrs = NULL; + } + + 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 (node->child == NULL) { + errno = EINVAL; + goto fail; + } + + 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); + ec_free(node->table[i].attr_name); + 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 = 2; + return 0; +} + +static const struct ec_config_schema ec_node_re_lex_dict[] = { + { + .key = "pattern", + .desc = "The pattern to match.", + .type = EC_CONFIG_TYPE_STRING, + }, + { + .key = "keep", + .desc = "Whether to keep or drop the string matching " + "the regular expression.", + .type = EC_CONFIG_TYPE_BOOL, + }, + { + .key = "attr", + .desc = "The optional attribute name to attach.", + .type = EC_CONFIG_TYPE_STRING, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static const struct ec_config_schema ec_node_re_lex_elt[] = { + { + .desc = "A pattern element.", + .type = EC_CONFIG_TYPE_DICT, + .subschema = ec_node_re_lex_dict, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static const struct ec_config_schema ec_node_re_lex_schema[] = { + { + .key = "patterns", + .desc = "The list of patterns elements.", + .type = EC_CONFIG_TYPE_LIST, + .subschema = ec_node_re_lex_elt, + }, + { + .key = "child", + .desc = "The child node.", + .type = EC_CONFIG_TYPE_NODE, + }, + { + .type = EC_CONFIG_TYPE_NONE, + }, +}; + +static int ec_node_re_lex_set_config(struct ec_node *gen_node, + const struct ec_config *config) +{ + struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node; + struct regexp_pattern *table = NULL; + const struct ec_config *patterns, *child, *elt, *pattern, *keep, *attr; + char *pattern_str = NULL, *attr_name = NULL; + ssize_t i, n = 0; + int ret; + + child = ec_config_dict_get(config, "child"); + if (child == NULL) + goto fail; + if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) { + errno = EINVAL; + goto fail; + } + + patterns = ec_config_dict_get(config, "patterns"); + if (patterns != NULL) { + n = ec_config_count(patterns); + if (n < 0) + goto fail; + + table = ec_calloc(n, sizeof(*table)); + if (table == NULL) + goto fail; + + n = 0; + TAILQ_FOREACH(elt, &patterns->list, next) { + if (ec_config_get_type(elt) != EC_CONFIG_TYPE_DICT) { + errno = EINVAL; + goto fail; + } + pattern = ec_config_dict_get(elt, "pattern"); + if (pattern == NULL) { + errno = EINVAL; + goto fail; + } + if (ec_config_get_type(pattern) != EC_CONFIG_TYPE_STRING) { + errno = EINVAL; + goto fail; + } + keep = ec_config_dict_get(elt, "keep"); + if (keep == NULL) { + errno = EINVAL; + goto fail; + } + if (ec_config_get_type(keep) != EC_CONFIG_TYPE_BOOL) { + errno = EINVAL; + goto fail; + } + attr = ec_config_dict_get(elt, "attr"); + if (attr != NULL && ec_config_get_type(attr) != + EC_CONFIG_TYPE_STRING) { + errno = EINVAL; + goto fail; + } + pattern_str = ec_strdup(pattern->string); + if (pattern_str == NULL) + goto fail; + if (attr != NULL && attr->string != NULL) { + attr_name = ec_strdup(attr->string); + if (attr_name == NULL) + goto fail; + } + + ret = regcomp(&table[n].r, pattern_str, REG_EXTENDED); + if (ret != 0) { + EC_LOG(EC_LOG_ERR, + "Regular expression <%s> compilation failed: %d\n", + pattern_str, ret); + if (ret == REG_ESPACE) + errno = ENOMEM; + else + errno = EINVAL; + goto fail; + } + table[n].pattern = pattern_str; + table[n].keep = keep->boolean; + table[n].attr_name = attr_name; + pattern_str = NULL; + attr_name = NULL; + + n++; + } + } + + if (node->child != NULL) + ec_node_free(node->child); + node->child = ec_node_clone(child->node); + for (i = 0; i < (ssize_t)node->len; i++) { + ec_free(node->table[i].pattern); + regfree(&node->table[i].r); + } + ec_free(node->table); + node->table = table; + node->len = n; + + return 0; + +fail: + if (table != NULL) { + for (i = 0; i < n; i++) { + if (table[i].pattern != NULL) { + ec_free(table[i].pattern); + regfree(&table[i].r); + } + } + } + ec_free(table); + ec_free(pattern_str); + return -1; +} + +static struct ec_node_type ec_node_re_lex_type = { + .name = "re_lex", + .schema = ec_node_re_lex_schema, + .set_config = ec_node_re_lex_set_config, + .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, + const char *attr_name) +{ + const struct ec_config *cur_config = NULL; + struct ec_config *config = NULL, *patterns = NULL, *elt = NULL; + int ret; + + if (ec_node_check_type(gen_node, &ec_node_re_lex_type) < 0) + goto fail; + + elt = ec_config_dict(); + if (elt == NULL) + goto fail; + if (ec_config_dict_set(elt, "pattern", ec_config_string(pattern)) < 0) + goto fail; + if (ec_config_dict_set(elt, "keep", ec_config_bool(keep)) < 0) + goto fail; + if (attr_name != NULL) { + if (ec_config_dict_set(elt, "attr", + ec_config_string(attr_name)) < 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; + + patterns = ec_config_dict_get(config, "patterns"); + if (patterns == NULL) { + patterns = ec_config_list(); + if (patterns == NULL) + goto fail; + + if (ec_config_dict_set(config, "patterns", patterns) < 0) + goto fail; /* patterns list is freed on error */ + } + + if (ec_config_list_add(patterns, elt) < 0) { + elt = NULL; + goto fail; + } + elt = NULL; + + ret = ec_node_set_config(gen_node, config); + config = NULL; /* freed */ + if (ret < 0) + goto fail; + + return 0; + +fail: + ec_config_free(config); + ec_config_free(elt); + return -1; +} + +static int +ec_node_re_lex_set_child(struct ec_node *gen_node, struct ec_node *child) +{ + const struct ec_config *cur_config = NULL; + struct ec_config *config = NULL; + int ret; + + if (ec_node_check_type(gen_node, &ec_node_re_lex_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; + + if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) { + child = NULL; /* freed */ + goto fail; + } + child = NULL; /* freed */ + + 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_re_lex(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_re_lex_type, id); + if (gen_node == NULL) + goto fail; + + if (ec_node_re_lex_set_child(gen_node, child) < 0) { + child = NULL; /* freed */ + goto fail; + } + + return gen_node; + +fail: + ec_node_free(gen_node); + ec_node_free(child); + return NULL; +} + +/* 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, NULL); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "[0-9]+", 1, NULL); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "=", 1, NULL); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "-", 1, NULL); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "\\+", 1, NULL); + testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp"); + ret = ec_node_re_lex_add(node, "[ ]+", 0, NULL); + 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/src/ecoli_node_seq.c b/src/ecoli_node_seq.c new file mode 100644 index 0000000..ff0c5de --- /dev/null +++ b/src/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/src/ecoli_node_sh_lex.c b/src/ecoli_node_sh_lex.c new file mode 100644 index 0000000..e27f21b --- /dev/null +++ b/src/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/src/ecoli_node_space.c b/src/ecoli_node_space.c new file mode 100644 index 0000000..761ed76 --- /dev/null +++ b/src/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/src/ecoli_node_str.c b/src/ecoli_node_str.c new file mode 100644 index 0000000..d53ea39 --- /dev/null +++ b/src/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/src/ecoli_node_subset.c b/src/ecoli_node_subset.c new file mode 100644 index 0000000..e3184ef --- /dev/null +++ b/src/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/src/ecoli_parse.c b/src/ecoli_parse.c new file mode 100644 index 0000000..3d6be77 --- /dev/null +++ b/src/ecoli_parse.c @@ -0,0 +1,544 @@ +/* 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); +} + +// XXX what is returned if no match ?? +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) +{ + if (parse == NULL) + return NULL; + + 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/src/ecoli_string.c b/src/ecoli_string.c new file mode 100644 index 0000000..fd427b4 --- /dev/null +++ b/src/ecoli_string.c @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#include +#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; +} + +bool ec_str_is_space(const char *s) +{ + while (*s) { + if (!isspace(*s)) + return false; + s++; + } + return true; +} diff --git a/src/ecoli_strvec.c b/src/ecoli_strvec.c new file mode 100644 index 0000000..98a952f --- /dev/null +++ b/src/ecoli_strvec.c @@ -0,0 +1,536 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2016, Olivier MATZ + */ + +#define _GNU_SOURCE /* qsort_r */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +EC_LOG_TYPE_REGISTER(strvec); + +struct ec_strvec_elt { + unsigned int refcnt; + char *str; + struct ec_keyval *attrs; +}; + +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; +} + +static struct ec_strvec_elt * +__ec_strvec_elt(const char *s) +{ + struct ec_strvec_elt *elt; + + elt = ec_calloc(1, sizeof(*elt)); + if (elt == NULL) + return NULL; + + elt->str = ec_strdup(s); + if (elt->str == NULL) { + ec_free(elt); + return NULL; + } + elt->refcnt = 1; + + return elt; +} + +static void +__ec_strvec_elt_free(struct ec_strvec_elt *elt) +{ + elt->refcnt--; + if (elt->refcnt == 0) { + ec_free(elt->str); + ec_keyval_free(elt->attrs); + ec_free(elt); + } +} + +int ec_strvec_set(struct ec_strvec *strvec, size_t idx, const char *s) +{ + struct ec_strvec_elt *elt; + + if (strvec == NULL || s == NULL || idx >= strvec->len) { + errno = EINVAL; + return -1; + } + + elt = __ec_strvec_elt(s); + if (elt == NULL) + return -1; + + __ec_strvec_elt_free(strvec->vec[idx]); + strvec->vec[idx] = elt; + + return 0; +} + +int ec_strvec_add(struct ec_strvec *strvec, const char *s) +{ + struct ec_strvec_elt *elt, **new_vec; + + if (strvec == NULL || s == NULL) { + errno = EINVAL; + return -1; + } + + new_vec = ec_realloc(strvec->vec, + sizeof(*strvec->vec) * (strvec->len + 1)); + if (new_vec == NULL) + return -1; + + strvec->vec = new_vec; + + elt = __ec_strvec_elt(s); + if (elt == NULL) + return -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) +{ + if (strvec->len == 0) { + errno = EINVAL; + return -1; + } + + __ec_strvec_elt_free(strvec->vec[strvec->len - 1]); + 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]; + __ec_strvec_elt_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; +} + +const struct ec_keyval *ec_strvec_get_attrs(const struct ec_strvec *strvec, + size_t idx) +{ + if (strvec == NULL || idx >= strvec->len) { + errno = EINVAL; + return NULL; + } + + return strvec->vec[idx]->attrs; +} + +int ec_strvec_set_attrs(struct ec_strvec *strvec, size_t idx, + struct ec_keyval *attrs) +{ + struct ec_strvec_elt *elt; + + if (strvec == NULL || idx >= strvec->len) { + errno = EINVAL; + goto fail; + } + + elt = strvec->vec[idx]; + if (elt->refcnt > 1) { + if (ec_strvec_set(strvec, idx, elt->str) < 0) + goto fail; + elt = strvec->vec[idx]; + } + + if (elt->attrs != NULL) + ec_keyval_free(elt->attrs); + + elt->attrs = attrs; + + return 0; + +fail: + ec_keyval_free(attrs); + return -1; +} + +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; + const struct ec_keyval *const_attrs = NULL; + struct ec_keyval *attrs = 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; + } + attrs = ec_keyval(); + if (attrs == NULL) { + EC_TEST_ERR("cannot create attrs\n"); + goto fail; + } + if (ec_keyval_set(attrs, "key", "value", NULL) < 0) { + EC_TEST_ERR("cannot set attr\n"); + goto fail; + } + if (ec_strvec_set_attrs(strvec, 1, attrs) < 0) { + attrs = NULL; + EC_TEST_ERR("cannot set attrs in strvec\n"); + goto fail; + } + attrs = NULL; + + ec_strvec_sort(strvec, NULL); + + /* attrs are now at index 0 after sorting */ + const_attrs = ec_strvec_get_attrs(strvec, 0); + if (const_attrs == NULL) { + EC_TEST_ERR("cannot get attrs\n"); + goto fail; + } + testres |= EC_TEST_CHECK( + ec_keyval_has_key(const_attrs, "key"), "cannot get attrs key\n"); + + 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_keyval_free(attrs); + 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/src/ecoli_test.c b/src/ecoli_test.c new file mode 100644 index 0000000..b090bd3 --- /dev/null +++ b/src/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/src/ecoli_vec.c b/src/ecoli_vec.c new file mode 100644 index 0000000..fe8c572 --- /dev/null +++ b/src/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/src/ecoli_yaml.c b/src/ecoli_yaml.c new file mode 100644 index 0000000..84007d5 --- /dev/null +++ b/src/ecoli_yaml.c @@ -0,0 +1,550 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#include +#include +#include +#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 associations yaml_node <-> ec_node */ +struct enode_table { + struct pair *pair; + size_t len; +}; + +static struct ec_node * +parse_ec_node(struct enode_table *table, const yaml_document_t *document, + const yaml_node_t *ynode); + +static struct ec_config * +parse_ec_config_list(struct enode_table *table, + 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_table *table, + 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_table *table, + const yaml_node_t *ynode, struct ec_node *enode) +{ + struct pair *pair = NULL; + + pair = realloc(table->pair, (table->len + 1) * sizeof(*pair)); + if (pair == NULL) + return -1; + + ec_node_clone(enode); + pair[table->len].ynode = ynode; + pair[table->len].enode = enode; + table->pair = pair; + table->len++; + + return 0; +} + +static void +free_table(struct enode_table *table) +{ + size_t i; + + for (i = 0; i < table->len; i++) + ec_node_free(table->pair[i].enode); + free(table->pair); +} + +static struct ec_config * +parse_ec_config(struct enode_table *table, + 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(table, document, ynode); + if (enode == NULL) + goto fail; + config = ec_config_node(enode); + enode = NULL; + 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(table, 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(table, 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_table *table, + 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; + + 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(table, 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_table *table, + 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(table, 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_table *table, + 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; + char *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 = ec_strdup(value_str); + if (help == NULL) { + fprintf(stderr, "Failed to allocate help\n"); + goto fail; + } + } + } + + /* 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(table, ynode, enode) < 0) { + fprintf(stderr, "Cannot add node in table\n"); + goto fail; + } + + /* 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(table, schema, document, ynode); + if (config == NULL) + goto fail; + + if (ec_node_set_config(enode, config) < 0) { + config = NULL; /* freed */ + fprintf(stderr, "Failed to set config\n"); + goto fail; + } + config = NULL; /* freed */ + + if (help != NULL) { + if (ec_keyval_set(ec_node_attrs(enode), "help", help, + ec_free_func) < 0) { + fprintf(stderr, "Failed to set help\n"); + help = NULL; + goto fail; + } + help = NULL; + } + + /* add attributes (all as string) */ + //XXX + + return enode; + +fail: + ec_node_free(enode); + ec_config_free(config); + ec_free(help); + + return NULL; +} + +static struct ec_node * +parse_document(struct enode_table *table, + const yaml_document_t *document) +{ + yaml_node_t *node; + + node = document->nodes.start; + return parse_ec_node(table, document, node); +} + +struct ec_node * +ec_yaml_import(const char *filename) +{ + FILE *file; + yaml_parser_t parser; + yaml_document_t document; + struct ec_node *root = NULL; + struct enode_table table; + + memset(&table, 0, sizeof(table)); + + 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; + } + + root = parse_document(&table, &document); + if (root == NULL) { + fprintf(stderr, "Failed to parse document\n"); + goto fail; + } + + yaml_document_delete(&document); + yaml_parser_delete(&parser); + fclose(file); + free_table(&table); + + return root; + +fail: + yaml_document_delete(&document); +fail_no_doc: + yaml_parser_delete(&parser); + if (file != NULL) + fclose(file); + free_table(&table); + ec_node_free(root); + + return NULL; +} diff --git a/src/editline.c b/src/editline.c new file mode 100644 index 0000000..449aebb --- /dev/null +++ b/src/editline.c @@ -0,0 +1,544 @@ +/* + * Copyright 2018 6WIND S.A. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "string_utils.h" +#include "editline.h" + +#define NC_CLI_HISTORY_SIZE 128 + +struct nc_cli_editline { + EditLine *editline; + History *history; + HistEvent histev; + bool break_received; + nc_cli_editline_complete_t complete; + FILE *null_out; + bool interactive; + bool incomplete_line; + char *full_line; + char *(*prompt_cb)(EditLine *); +}; + +struct nc_cli_editline *nc_cli_el; + +static int check_quotes(const char *str) +{ + char quote = 0; + size_t i = 0; + + while (str[i] != '\0') { + if (quote == 0) { + if (str[i] == '"' || str[i] == '\'') { + quote = str[i]; + } + i++; + continue; + } else { + if (str[i] == quote) { + i++; + quote = 0; + } else if (str[i] == '\\' && str[i+1] == quote) { + i += 2; + } else { + i++; + } + continue; + } + } + + return quote; +} + +static int +editline_break(EditLine *editline, int c) +{ + struct nc_cli_editline *el; + void *ptr; + + (void)c; + + if (el_get(editline, EL_CLIENTDATA, &ptr)) + return CC_ERROR; + + el = ptr; + el->break_received = true; + nc_cli_printf(el, "\n"); + + return CC_EOF; +} + +static int +editline_suspend(EditLine *editline, int c) +{ + (void)editline; + (void)c; + + kill(getpid(), SIGSTOP); + + return CC_NORM; +} + +int +editline_get_screen_size(struct nc_cli_editline *el, size_t *rows, size_t *cols) +{ + int w, h; + + if (rows != NULL) { + if (el_get(el->editline, EL_GETTC, "li", &h, (void *)0)) + return -1; + *rows = h; + } + if (cols != NULL) { + if (el_get(el->editline, EL_GETTC, "co", &w, (void *)0)) + return -1; + *cols = w; + } + return 0; +} + +FILE * +nc_cli_editline_get_file(struct nc_cli_editline *el, int num) +{ + FILE *f; + + if (el == NULL) + return NULL; + if (num > 2) + return NULL; + if (el_get(el->editline, EL_GETFP, num, &f)) + return NULL; + + return f; +} + +/* match the prototype expected by qsort() */ +static int +strcasecmp_cb(const void *p1, const void *p2) +{ + return strcasecmp(*(char * const *)p1, *(char * const *)p2); +} + +/* Show the matches as a multi-columns list */ +int +nc_cli_editline_print_cols(struct nc_cli_editline *el, + char const * const *matches, size_t n) +{ + size_t max_strlen = 0, len, i, j, ncols; + size_t width, height; + const char *space; + char **matches_copy = NULL; + + nc_cli_printf(nc_cli_el, "\n"); + if (n == 0) + return 0; + + if (editline_get_screen_size(el, &height, &width) < 0) + width = 80; + + /* duplicate the matches table, and sort it */ + matches_copy = calloc(n, sizeof(const char *)); + if (matches_copy == NULL) + return -1; + memcpy(matches_copy, matches, sizeof(const char *) * n); + qsort(matches_copy, n, sizeof(char *), strcasecmp_cb); + + /* get max string length */ + for (i = 0; i < n; i++) { + len = strlen(matches_copy[i]); + if (len > max_strlen) + max_strlen = len; + } + + /* write the columns */ + ncols = width / (max_strlen + 4); + if (ncols == 0) + ncols = 1; + for (i = 0; i < n; i+= ncols) { + for (j = 0; j < ncols; j++) { + if (i + j >= n) + break; + if (j == 0) + space = ""; + else + space = " "; + nc_cli_printf(nc_cli_el, "%s%-*s", space, + (int)max_strlen, matches[i+j]); + } + nc_cli_printf(nc_cli_el, "\n"); + } + + free(matches_copy); + return 0; +} + +static int +editline_complete(EditLine *editline, int c) +{ + enum nc_cli_editline_complete_status ret; + const LineInfo *line_info; + struct nc_cli_editline *el; + int len; + char *line; + void *ptr; + + if (el_get(editline, EL_CLIENTDATA, &ptr)) + return CC_ERROR; + + el = ptr; + + if (el->complete == NULL) + return CC_NORM; + + line_info = el_line(editline); + if (line_info == NULL) + return CC_ERROR; + + len = line_info->cursor - line_info->buffer; + if (asprintf(&line, "%s%.*s", el->full_line ? : "", len, + line_info->buffer) < 0) + return CC_ERROR; + + if (c == '?' && check_quotes(line) != 0) { + free(line); + el_insertstr(editline, "?"); + return CC_REFRESH; + } + + ret = el->complete(c, line); + free(line); + + if (ret == ERROR) + return CC_ERROR; + else if (ret == REDISPLAY) + return CC_REDISPLAY; + else + return CC_REFRESH; +} + +static bool is_blank_string(const char *s) +{ + while (*s) { + if (!isspace(*s)) + return false; + s++; + } + return true; +} + +static char * +multiline_prompt_cb(EditLine *e) +{ + (void)e; + return strdup("... "); +} + +const char * +nc_cli_editline_edit(struct nc_cli_editline *el) +{ + const char *line; + int count; + + assert(el->editline != NULL); + + if (el->incomplete_line == false) { + free(el->full_line); + el->full_line = NULL; + } + + el->break_received = false; + + if (el->incomplete_line) + el_set(el->editline, EL_PROMPT, multiline_prompt_cb); + else + el_set(el->editline, EL_PROMPT, el->prompt_cb); + + line = el_gets(el->editline, &count); + + if (line == NULL && el->break_received) { + free(el->full_line); + el->full_line = NULL; + el->incomplete_line = false; + return ""; /* abort current line */ + } + + if (line == NULL || astrcat(&el->full_line, line) < 0) { + free(el->full_line); + el->full_line = NULL; + el->incomplete_line = false; + return NULL; /* error / eof */ + } + + if (check_quotes(el->full_line) != 0) { + el->incomplete_line = true; + return ""; + } + + el->incomplete_line = false; + if (el->history != NULL && !is_blank_string(el->full_line)) + history(el->history, &el->histev, + H_ENTER, el->full_line); + + return el->full_line; +} + +int +nc_cli_editline_register_complete(struct nc_cli_editline *el, + nc_cli_editline_complete_t complete) +{ + const char *name; + + if (el_set(el->editline, EL_ADDFN, "ed-complete", + "Complete buffer", + editline_complete)) + return -1; + + if (complete != NULL) + name = "ed-complete"; + else + name = "ed-unassigned"; + if (el_set(el->editline, EL_BIND, "^I", name, NULL)) + return -1; + + if (complete != NULL) + name = "ed-complete"; + else + name = "ed-insert"; + if (el_set(el->editline, EL_BIND, "?", name, NULL)) + return -1; + + el->complete = complete; + + return 0; +} + +int +nc_cli_editline_insert_str(struct nc_cli_editline *el, const char *str) +{ + return el_insertstr(el->editline, str); +} + +int nc_cli_printf(struct nc_cli_editline *el, const char *format, ...) +{ + FILE *out = nc_cli_editline_get_file(el, 1); + va_list ap; + int ret; + + if (out == NULL) + out = stdout; + + va_start(ap, format); + ret = vfprintf(out, format, ap); + va_end(ap); + + return ret; +} + +int nc_cli_eprintf(struct nc_cli_editline *el, const char *format, ...) +{ + FILE *out = nc_cli_editline_get_file(el, 2); + va_list ap; + int ret; + + if (out == NULL) + out = stderr; + + va_start(ap, format); + ret = vfprintf(out, format, ap); + va_end(ap); + + return ret; +} + +bool +nc_cli_editline_is_interactive(struct nc_cli_editline *el) +{ + return el->interactive; +} + +bool +nc_cli_editline_is_running(struct nc_cli_editline *el) +{ + return el == nc_cli_el; +} + +void +nc_cli_editline_start(struct nc_cli_editline *el) +{ + nc_cli_el = el; +} + +void +nc_cli_editline_stop(struct nc_cli_editline *el) +{ + if (el == nc_cli_el) + nc_cli_el = NULL; +} + +int +nc_cli_editline_getc(struct nc_cli_editline *el) +{ + char c; + + if (el->interactive == false) + return -1; + + if (el_getc(el->editline, &c) != 1) + return -1; + + return c; +} + +int +nc_cli_editline_resize(struct nc_cli_editline *el) +{ + el_resize(el->editline); + return 0; +} + +int nc_cli_editline_set_prompt_cb(struct nc_cli_editline *el, + char *(*prompt_cb)(EditLine *el)) +{ + el->prompt_cb = prompt_cb; + return 0; +} + +int +nc_cli_editline_mask_interrupts(bool do_mask) +{ + const char *setty = do_mask ? "-isig" : "+isig"; + + if (nc_cli_el == NULL) + return -1; + + if (nc_cli_el->interactive == false) + return 0; + + if (el_set(nc_cli_el->editline, EL_SETTY, "-d", setty, NULL)) + return -1; + + return 0; +} + +struct nc_cli_editline * +nc_cli_editline_init(FILE *f_in, FILE *f_out, FILE *f_err, bool interactive, + char *(*prompt_cb)(EditLine *el)) +{ + struct nc_cli_editline *el = NULL; + + el = calloc(1, sizeof(*el)); + if (el == NULL) + goto fail; + if (f_in == NULL) { + errno = EINVAL; + goto fail; + } + if (f_out == NULL || f_err == NULL) { + el->null_out = fopen("/dev/null", "w"); + if (el->null_out == NULL) + goto fail; + } + if (f_out == NULL) + f_out = el->null_out; + if (f_err == NULL) + f_err = el->null_out; + + el->interactive = interactive; + el->prompt_cb = prompt_cb; + + el->editline = el_init("nc-cli", f_in, f_out, f_err); + if (el->editline == NULL) + goto fail; + + if (el_set(el->editline, EL_SIGNAL, 1)) + goto fail; + + if (el_set(el->editline, EL_CLIENTDATA, el)) + goto fail; + + if (el_set(el->editline, EL_PROMPT, prompt_cb)) + goto fail; + + if (interactive == false) + goto end; + + if (el_set(el->editline, EL_PREP_TERM, 0)) + goto fail; + + if (el_set(el->editline, EL_EDITOR, "emacs")) + goto fail; + + if (el_set(el->editline, EL_SETTY, "-d", "-isig", NULL)) + goto fail; + if (el_set(el->editline, EL_ADDFN, "ed-break", + "Break and flush the buffer", + editline_break)) + goto fail; + if (el_set(el->editline, EL_BIND, "^C", "ed-break", NULL)) + goto fail; + if (el_set(el->editline, EL_ADDFN, "ed-suspend", + "Suspend the terminal", + editline_suspend)) + goto fail; + if (el_set(el->editline, EL_BIND, "^Z", "ed-suspend", NULL)) + goto fail; + if (el_set(el->editline, EL_BIND, "^W", "ed-delete-prev-word", NULL)) + goto fail; + + el->history = history_init(); + if (!el->history) + goto fail; + if (history(el->history, &el->histev, H_SETSIZE, + NC_CLI_HISTORY_SIZE) < 0) + goto fail; + if (history(el->history, &el->histev, + H_SETUNIQUE, 1)) + goto fail; + if (el_set(el->editline, EL_HIST, history, + el->history)) + goto fail; + +end: + return el; + +fail: + if (el != NULL) { + if (el->null_out != NULL) + fclose(el->null_out); + if (el->history != NULL) + history_end(el->history); + if (el->editline != NULL) + el_end(el->editline); + free(el); + } + return NULL; +} + +void +nc_cli_editline_free(struct nc_cli_editline *el) +{ + if (el == NULL) + return; + nc_cli_editline_stop(el); + if (el->null_out != NULL) + fclose(el->null_out); + if (el->history != NULL) + history_end(el->history); + el_end(el->editline); + free(el->full_line); + free(el); +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..4c9860f --- /dev/null +++ b/src/meson.build @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2018, Olivier MATZ + +inc = include_directories('../include') + +libecoli_sources = [ + 'ecoli_assert.c', + 'ecoli_complete.c', + 'ecoli_config.c', + 'ecoli_editline.c', + 'ecoli_init.c', + 'ecoli_keyval.c', + 'ecoli_log.c', + 'ecoli_malloc.c', + 'ecoli_murmurhash.c', + 'ecoli_node_any.c', + 'ecoli_node.c', + 'ecoli_node_cmd.c', +# 'ecoli_node_cond.c', + 'ecoli_node_dynamic.c', + 'ecoli_node_empty.c', + 'ecoli_node_expr.c', + 'ecoli_node_expr_test.c', + 'ecoli_node_file.c', + 'ecoli_node_helper.c', + 'ecoli_node_int.c', + 'ecoli_node_many.c', + 'ecoli_node_none.c', + 'ecoli_node_once.c', + 'ecoli_node_option.c', + 'ecoli_node_or.c', + 'ecoli_node_re.c', + 'ecoli_node_re_lex.c', + 'ecoli_node_seq.c', + 'ecoli_node_sh_lex.c', + 'ecoli_node_space.c', + 'ecoli_node_str.c', + 'ecoli_node_subset.c', + 'ecoli_parse.c', + 'ecoli_string.c', + 'ecoli_strvec.c', + 'ecoli_test.c', + 'ecoli_vec.c', + 'ecoli_yaml.c', +] +libecoli = shared_library('ecoli', + libecoli_sources, + include_directories : inc, + dependencies : [edit_dep, yaml_dep], + install : true) diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..f61dbc6 --- /dev/null +++ b/test/meson.build @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2018, Olivier MATZ + +inc = include_directories('../include') + +test_sources = [ + 'test.c', +] + +ecoli_test = executable('ecoli-test', + test_sources, + include_directories : inc, + link_with : libecoli) + +test('libecoli test', ecoli_test) diff --git a/todo.txt b/todo.txt index 2b84064..acb7069 100644 --- a/todo.txt +++ b/todo.txt @@ -123,6 +123,8 @@ X file + partial completion - fusion node: need to match several children, same for completion? - float - not +- reparse: parse a tree with received strvec, then another tree + with strvec generated from first tree encoding ======== @@ -421,3 +423,17 @@ c --------------- +about split in several libraries + +There are several options: + +1/ one library, config options to select libyaml, libedit + - need to manage dependencies in build system + +2/ one library for ecoli-core, one for ecoli-yaml, one for + ecoli-edit + - extra complexity + +3/ one library with core + yaml + edit + dependency is managed at runtime +