add meson support
[protos/libecoli.git] / src / ecoli_parse.c
diff --git a/src/ecoli_parse.c b/src/ecoli_parse.c
new file mode 100644 (file)
index 0000000..3d6be77
--- /dev/null
@@ -0,0 +1,544 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_assert.h>
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+#include <ecoli_node_sh_lex.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_parse.h>
+
+EC_LOG_TYPE_REGISTER(parse);
+
+TAILQ_HEAD(ec_parse_list, ec_parse);
+
+struct ec_parse {
+       TAILQ_ENTRY(ec_parse) next;
+       struct ec_parse_list children;
+       struct ec_parse *parent;
+       const struct ec_node *node;
+       struct ec_strvec *strvec;
+       struct ec_keyval *attrs;
+};
+
+static int __ec_node_parse_child(const struct ec_node *node,
+                               struct ec_parse *state,
+                               bool is_root, const struct ec_strvec *strvec)
+{
+       struct ec_strvec *match_strvec;
+       struct ec_parse *child = NULL;
+       int ret;
+
+       if (ec_node_type(node)->parse == NULL) {
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       if (!is_root) {
+               child = ec_parse(node);
+               if (child == NULL)
+                       return -1;
+
+               ec_parse_link_child(state, child);
+       } else {
+               child = state;
+       }
+       ret = ec_node_type(node)->parse(node, child, strvec);
+       if (ret < 0)
+               goto fail;
+
+       if (ret == EC_PARSE_NOMATCH) {
+               if (!is_root) {
+                       ec_parse_unlink_child(state, child);
+                       ec_parse_free(child);
+               }
+               return ret;
+       }
+
+       match_strvec = ec_strvec_ndup(strvec, 0, ret);
+       if (match_strvec == NULL)
+               goto fail;
+
+       child->strvec = match_strvec;
+
+       return ret;
+
+fail:
+       if (!is_root) {
+               ec_parse_unlink_child(state, child);
+               ec_parse_free(child);
+       }
+       return -1;
+}
+
+int ec_node_parse_child(const struct ec_node *node, struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       assert(state != NULL);
+       return __ec_node_parse_child(node, state, false, strvec);
+}
+
+// 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);