From 7e8433684f65e5198480aa5a70b84af6330e44ad Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Thu, 11 Jan 2018 19:39:52 +0100 Subject: [PATCH] save --- lib/ecoli_completed.c | 12 ++- lib/ecoli_completed.h | 8 ++ lib/ecoli_keyval.c | 208 ++++++++++++++++++++++++++-------------- lib/ecoli_keyval.h | 4 + lib/ecoli_node_cmd.c | 6 +- lib/ecoli_node_expr.c | 6 +- lib/ecoli_node_many.c | 7 +- lib/ecoli_node_once.c | 4 +- lib/ecoli_node_option.c | 5 +- lib/ecoli_node_re_lex.c | 19 ++-- lib/ecoli_node_seq.c | 8 +- lib/ecoli_node_sh_lex.c | 19 ++-- lib/ecoli_node_subset.c | 10 +- lib/ecoli_parsed.c | 93 ++++++++++++++++-- lib/ecoli_parsed.h | 181 ++++++++++++++++++++++++++++++---- lib/main-readline.c | 84 ++++++++++------ lib/todo.txt | 25 +++-- 17 files changed, 519 insertions(+), 180 deletions(-) diff --git a/lib/ecoli_completed.c b/lib/ecoli_completed.c index 4836803..a39c719 100644 --- a/lib/ecoli_completed.c +++ b/lib/ecoli_completed.c @@ -103,7 +103,7 @@ ec_node_complete_child(struct ec_node *node, child_state = ec_parsed(); if (child_state == NULL) return -ENOMEM; - child_state->node = node; + ec_parsed_set_node(child_state, node); ec_parsed_add_child(parsed_state, child_state); ret = node->type->complete(node, completed, child_state, strvec); @@ -119,7 +119,7 @@ ec_node_complete_child(struct ec_node *node, #endif ec_parsed_del_child(parsed_state, child_state); - assert(TAILQ_EMPTY(&child_state->children)); + assert(!ec_parsed_has_child(child_state)); ec_parsed_free(child_state); return 0; @@ -223,7 +223,7 @@ ec_completed_item(struct ec_parsed *state, const struct ec_node *node) /* write path in array */ for (p = state, len = 0; p != NULL; p = ec_parsed_get_parent(p), len++) - item->path[len] = p->node; + item->path[len] = ec_parsed_get_node(p); item->type = EC_NO_MATCH; item->node = node; @@ -371,6 +371,12 @@ ec_completed_item_get_type(const struct ec_completed_item *item) return item->type; } +const struct ec_node * +ec_completed_item_get_node(const struct ec_completed_item *item) +{ + return item->node; +} + void ec_completed_item_free(struct ec_completed_item *item) { if (item == NULL) diff --git a/lib/ecoli_completed.h b/lib/ecoli_completed.h index e66cd71..4054a1a 100644 --- a/lib/ecoli_completed.h +++ b/lib/ecoli_completed.h @@ -154,6 +154,14 @@ ec_completed_item_get_display(const struct ec_completed_item *item); enum ec_completed_type ec_completed_item_get_type(const struct ec_completed_item *item); +/** + * Get the node associated to a completion item. + * + * + */ +const struct ec_node * +ec_completed_item_get_node(const struct ec_completed_item *item); + /** * * diff --git a/lib/ecoli_keyval.c b/lib/ecoli_keyval.c index dbc5522..5345ccf 100644 --- a/lib/ecoli_keyval.c +++ b/lib/ecoli_keyval.c @@ -49,19 +49,24 @@ EC_LOG_TYPE_REGISTER(keyval); static uint32_t ec_keyval_seed; struct ec_keyval_elt { - LIST_ENTRY(ec_keyval_elt) next; char *key; void *val; uint32_t hash; ec_keyval_elt_free_t free; + unsigned int refcount; }; -LIST_HEAD(ec_keyval_elt_list, ec_keyval_elt); +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_list *table; + struct ec_keyval_elt_ref_list *table; }; struct ec_keyval *ec_keyval(void) @@ -75,10 +80,10 @@ struct ec_keyval *ec_keyval(void) return keyval; } -static struct ec_keyval_elt *ec_keyval_lookup(const struct ec_keyval *keyval, - const char *key) +static struct ec_keyval_elt_ref * +ec_keyval_lookup(const struct ec_keyval *keyval, const char *key) { - struct ec_keyval_elt *elt; + struct ec_keyval_elt_ref *ref; uint32_t h, mask = keyval->table_size - 1; if (keyval == NULL || key == NULL) { @@ -91,10 +96,10 @@ static struct ec_keyval_elt *ec_keyval_lookup(const struct ec_keyval *keyval, } h = ec_murmurhash3(key, strlen(key), ec_keyval_seed); - LIST_FOREACH(elt, &keyval->table[h & mask], next) { - if (strcmp(elt->key, key) == 0) { + LIST_FOREACH(ref, &keyval->table[h & mask], next) { + if (strcmp(ref->elt->key, key) == 0) { errno = 0; - return elt; + return ref; } } @@ -102,15 +107,21 @@ static struct ec_keyval_elt *ec_keyval_lookup(const struct ec_keyval *keyval, return NULL; } -static void ec_keyval_elt_free(struct ec_keyval_elt *elt) +static void ec_keyval_elt_ref_free(struct ec_keyval_elt_ref *ref) { - if (elt == NULL) + struct ec_keyval_elt *elt; + + if (ref == NULL) return; - ec_free(elt->key); - if (elt->free != NULL) - elt->free(elt->val); - ec_free(elt); + 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) @@ -120,27 +131,27 @@ bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key) void *ec_keyval_get(const struct ec_keyval *keyval, const char *key) { - struct ec_keyval_elt *elt; + struct ec_keyval_elt_ref *ref; - elt = ec_keyval_lookup(keyval, key); - if (elt == NULL) + ref = ec_keyval_lookup(keyval, key); + if (ref == NULL) return NULL; - return elt->val; + return ref->elt->val; } int ec_keyval_del(struct ec_keyval *keyval, const char *key) { - struct ec_keyval_elt *elt; + struct ec_keyval_elt_ref *ref; - elt = ec_keyval_lookup(keyval, key); - if (elt == NULL) + ref = ec_keyval_lookup(keyval, key); + if (ref == NULL) return -1; /* we could resize table here */ - LIST_REMOVE(elt, next); - ec_keyval_elt_free(elt); + LIST_REMOVE(ref, next); + ec_keyval_elt_ref_free(ref); keyval->len--; return 0; @@ -148,8 +159,8 @@ int ec_keyval_del(struct ec_keyval *keyval, const char *key) static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size) { - struct ec_keyval_elt_list *new_table; - struct ec_keyval_elt *elt; + 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))) { @@ -163,10 +174,11 @@ static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size) for (i = 0; i < keyval->table_size; i++) { while (!LIST_EMPTY(&keyval->table[i])) { - elt = LIST_FIRST(&keyval->table[i]); - LIST_REMOVE(elt, next); - LIST_INSERT_HEAD(&new_table[elt->hash & (new_size - 1)], - elt, next); + ref = LIST_FIRST(&keyval->table[i]); + LIST_REMOVE(ref, next); + LIST_INSERT_HEAD( + &new_table[ref->elt->hash & (new_size - 1)], + ref, next); } } @@ -177,27 +189,15 @@ static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size) return 0; } -int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val, - ec_keyval_elt_free_t free_cb) +static int +__ec_keyval_set(struct ec_keyval *keyval, struct ec_keyval_elt_ref *ref) { - struct ec_keyval_elt *elt; - uint32_t h, mask; size_t new_size; + uint32_t mask; int ret; - if (keyval == NULL || key == NULL) { - errno = EINVAL; - return -1; - } - - elt = ec_keyval_lookup(keyval, key); - if (elt != NULL) { - if (elt->free != NULL) - elt->free(elt->val); - elt->val = val; - elt->free = free_cb; - return 0; - } + /* remove previous entry if any */ + ec_keyval_del(keyval, ref->elt->key); if (keyval->len >= keyval->table_size) { if (keyval->table_size != 0) @@ -206,40 +206,62 @@ int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val, new_size = 1 << FACTOR; ret = ec_keyval_table_resize(keyval, new_size); if (ret < 0) - goto fail; + 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; - elt->val = val; - elt->free = free_cb; h = ec_murmurhash3(key, strlen(key), ec_keyval_seed); elt->hash = h; - mask = keyval->table_size - 1; - LIST_INSERT_HEAD(&keyval->table[h & mask], elt, next); - keyval->len++; + if (__ec_keyval_set(keyval, ref) < 0) + goto fail; return 0; fail: - if (free_cb != NULL) + if (free_cb != NULL && val != NULL) free_cb(val); - if (elt != NULL) { - ec_free(elt->key); - ec_free(elt); - } - - return ret; + ec_keyval_elt_ref_free(ref); + return -1; } void ec_keyval_free(struct ec_keyval *keyval) { - struct ec_keyval_elt *elt; + struct ec_keyval_elt_ref *ref; size_t i; if (keyval == NULL) @@ -247,9 +269,9 @@ void ec_keyval_free(struct ec_keyval *keyval) for (i = 0; i < keyval->table_size; i++) { while (!LIST_EMPTY(&keyval->table[i])) { - elt = LIST_FIRST(&keyval->table[i]); - LIST_REMOVE(elt, next); - ec_keyval_elt_free(elt); + ref = LIST_FIRST(&keyval->table[i]); + LIST_REMOVE(ref, next); + ec_keyval_elt_ref_free(ref); } } ec_free(keyval->table); @@ -263,7 +285,7 @@ size_t ec_keyval_len(const struct ec_keyval *keyval) void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval) { - struct ec_keyval_elt *elt; + struct ec_keyval_elt_ref *ref; size_t i; if (keyval == NULL) { @@ -273,13 +295,44 @@ void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval) fprintf(out, "keyval:\n"); for (i = 0; i < keyval->table_size; i++) { - LIST_FOREACH(elt, &keyval->table[i], next) { + LIST_FOREACH(ref, &keyval->table[i], next) { fprintf(out, " %s: %p\n", - elt->key, elt->val); + ref->elt->key, ref->elt->val); } } } +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; @@ -309,7 +362,7 @@ EC_INIT_REGISTER(ec_keyval_init); /* LCOV_EXCL_START */ static int ec_keyval_testcase(void) { - struct ec_keyval *keyval; + struct ec_keyval *keyval, *dup; char *val; size_t i; int ret; @@ -355,11 +408,23 @@ static int ec_keyval_testcase(void) ec_keyval_dump(stdout, keyval); for (i = 0; i < 100; i++) { - char buf[8]; - snprintf(buf, sizeof(buf), "k%zd", i); - ret = ec_keyval_set(keyval, buf, "val", NULL); + char key[8]; + snprintf(key, sizeof(key), "k%zd", i); + ret = ec_keyval_set(keyval, key, "val", NULL); EC_TEST_ASSERT_STR(ret == 0, "cannot set key"); } + dup = ec_keyval_dup(keyval); + EC_TEST_ASSERT_STR(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); + EC_TEST_ASSERT(val != NULL && !strcmp(val, "val")); + } + ec_keyval_free(dup); + dup = NULL; + } /* einval */ ret = ec_keyval_set(keyval, NULL, "val1", NULL); @@ -369,7 +434,6 @@ static int ec_keyval_testcase(void) ec_keyval_free(keyval); - return 0; } /* LCOV_EXCL_STOP */ diff --git a/lib/ecoli_keyval.h b/lib/ecoli_keyval.h index 3d739ff..5622b23 100644 --- a/lib/ecoli_keyval.h +++ b/lib/ecoli_keyval.h @@ -101,6 +101,7 @@ int ec_keyval_del(struct ec_keyval *keyval, const char *key); * 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); @@ -123,6 +124,9 @@ void ec_keyval_free(struct ec_keyval *keyval); */ size_t ec_keyval_len(const struct ec_keyval *keyval); +/* XXX help */ +struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval); + /** * Dump a hash table. * diff --git a/lib/ecoli_node_cmd.c b/lib/ecoli_node_cmd.c index 0aca9b1..3068fc0 100644 --- a/lib/ecoli_node_cmd.c +++ b/lib/ecoli_node_cmd.c @@ -353,9 +353,9 @@ static int ec_node_cmd_build(struct ec_node *gen_node) ret = -EINVAL; if (!ec_parsed_matches(p)) goto fail; - if (TAILQ_EMPTY(&p->children)) + if (!ec_parsed_has_child(p)) goto fail; - if (TAILQ_EMPTY(&TAILQ_FIRST(&p->children)->children)) + if (!ec_parsed_has_child(ec_parsed_get_first_child(p))) goto fail; ret = -ENOMEM; @@ -363,7 +363,7 @@ static int ec_node_cmd_build(struct ec_node *gen_node) if (cmd == NULL) goto fail; - TAILQ_FOREACH(child, &TAILQ_FIRST(&p->children)->children, next) { + EC_PARSED_FOREACH_CHILD(child, ec_parsed_get_first_child(p)) { ret = ec_node_expr_eval(&result, expr, child, &test_ops, node); if (ret < 0) diff --git a/lib/ecoli_node_expr.c b/lib/ecoli_node_expr.c index 9207e4d..58d4969 100644 --- a/lib/ecoli_node_expr.c +++ b/lib/ecoli_node_expr.c @@ -527,7 +527,7 @@ static int eval_expression(struct result *result, memset(result, 0, sizeof(*result)); memset(&child_result, 0, sizeof(child_result)); - type = get_node_type(expr_gen_node, parsed->node); + type = get_node_type(expr_gen_node, ec_parsed_get_node(parsed)); if (type == VAL) { ret = ops->eval_var(&result->val, userctx, parsed); if (ret < 0) @@ -538,9 +538,9 @@ static int eval_expression(struct result *result, result->op_type = type; } - TAILQ_FOREACH(child, &parsed->children, next) { + EC_PARSED_FOREACH_CHILD(child, parsed) { - type = get_node_type(expr_gen_node, child->node); + type = get_node_type(expr_gen_node, ec_parsed_get_node(child)); if (type == PAREN_OPEN) { open = child; continue; diff --git a/lib/ecoli_node_many.c b/lib/ecoli_node_many.c index b40b3bd..cc5f738 100644 --- a/lib/ecoli_node_many.c +++ b/lib/ecoli_node_many.c @@ -71,13 +71,14 @@ static int ec_node_many_parse(const struct ec_node *gen_node, } ret = ec_node_parse_child(node->child, state, childvec); + if (ret < 0) + goto fail; + ec_strvec_free(childvec); childvec = NULL; if (ret == EC_PARSED_NOMATCH) break; - else if (ret < 0) - goto fail; /* it matches an empty strvec, no need to continue */ if (ret == 0) { @@ -133,7 +134,7 @@ __ec_node_many_complete(struct ec_node_many *node, unsigned int max, goto fail; ret = ec_node_parse_child(node->child, parsed, childvec); - if (ret < 0 && ret != EC_PARSED_NOMATCH) + if (ret < 0) goto fail; ec_strvec_free(childvec); diff --git a/lib/ecoli_node_once.c b/lib/ecoli_node_once.c index 54aa67b..0df4bcd 100644 --- a/lib/ecoli_node_once.c +++ b/lib/ecoli_node_once.c @@ -60,10 +60,10 @@ count_node(struct ec_parsed *parsed, const struct ec_node *node) if (parsed == NULL) return 0; - if (parsed->node == node) + if (ec_parsed_get_node(parsed) == node) count++; - TAILQ_FOREACH(child, &parsed->children, next) + EC_PARSED_FOREACH_CHILD(child, parsed) count += count_node(child, node); return count; diff --git a/lib/ecoli_node_option.c b/lib/ecoli_node_option.c index 1dcdd88..ab4f9f3 100644 --- a/lib/ecoli_node_option.c +++ b/lib/ecoli_node_option.c @@ -57,10 +57,11 @@ ec_node_option_parse(const struct ec_node *gen_node, int ret; ret = ec_node_parse_child(node->child, state, strvec); + if (ret < 0) + return ret; + if (ret == EC_PARSED_NOMATCH) return 0; - else if (ret < 0) - return ret; return ret; } diff --git a/lib/ecoli_node_re_lex.c b/lib/ecoli_node_re_lex.c index 669c9fb..14b3e46 100644 --- a/lib/ecoli_node_re_lex.c +++ b/lib/ecoli_node_re_lex.c @@ -113,15 +113,16 @@ ec_node_re_lex_parse(const struct ec_node *gen_node, } ret = ec_node_parse_child(node->child, state, new_vec); - if (ret >= 0) { - if ((unsigned)ret == ec_strvec_len(new_vec)) { - ret = 1; - } else { - child_parsed = ec_parsed_get_last_child(state); - ec_parsed_del_child(state, child_parsed); - ec_parsed_free(child_parsed); - ret = EC_PARSED_NOMATCH; - } + if (ret < 0) + goto fail; + + if ((unsigned)ret == ec_strvec_len(new_vec)) { + ret = 1; + } else if (ret != EC_PARSED_NOMATCH) { + child_parsed = ec_parsed_get_last_child(state); + ec_parsed_del_child(state, child_parsed); + ec_parsed_free(child_parsed); + ret = EC_PARSED_NOMATCH; } ec_strvec_free(new_vec); diff --git a/lib/ecoli_node_seq.c b/lib/ecoli_node_seq.c index 9240e4f..2b33503 100644 --- a/lib/ecoli_node_seq.c +++ b/lib/ecoli_node_seq.c @@ -74,13 +74,15 @@ ec_node_seq_parse(const struct ec_node *gen_node, } ret = ec_node_parse_child(node->table[i], state, childvec); + if (ret < 0) + goto fail; + ec_strvec_free(childvec); childvec = NULL; + if (ret == EC_PARSED_NOMATCH) { ec_parsed_free_children(state); return EC_PARSED_NOMATCH; - } else if (ret < 0) { - goto fail; } len += ret; @@ -130,7 +132,7 @@ __ec_node_seq_complete(struct ec_node **table, size_t table_len, goto fail; ret = ec_node_parse_child(table[0], parsed, childvec); - if (ret < 0 && ret != EC_PARSED_NOMATCH) + if (ret < 0) goto fail; ec_strvec_free(childvec); diff --git a/lib/ecoli_node_sh_lex.c b/lib/ecoli_node_sh_lex.c index 87b3175..94d0d73 100644 --- a/lib/ecoli_node_sh_lex.c +++ b/lib/ecoli_node_sh_lex.c @@ -255,15 +255,16 @@ ec_node_sh_lex_parse(const struct ec_node *gen_node, } ret = ec_node_parse_child(node->child, state, new_vec); - if (ret >= 0) { - if ((unsigned)ret == ec_strvec_len(new_vec)) { - ret = 1; - } else { - child_parsed = ec_parsed_get_last_child(state); - ec_parsed_del_child(state, child_parsed); - ec_parsed_free(child_parsed); - ret = EC_PARSED_NOMATCH; - } + if (ret < 0) + goto fail; + + if ((unsigned)ret == ec_strvec_len(new_vec)) { + ret = 1; + } else if (ret != EC_PARSED_NOMATCH) { + child_parsed = ec_parsed_get_last_child(state); + ec_parsed_del_child(state, child_parsed); + ec_parsed_free(child_parsed); + ret = EC_PARSED_NOMATCH; } ec_strvec_free(new_vec); diff --git a/lib/ecoli_node_subset.c b/lib/ecoli_node_subset.c index 1803b4d..18de0cb 100644 --- a/lib/ecoli_node_subset.c +++ b/lib/ecoli_node_subset.c @@ -85,10 +85,11 @@ __ec_node_subset_parse(struct parse_result *out, struct ec_node **table, 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_PARSED_NOMATCH) continue; - else if (ret < 0) - goto fail; /* build a new table without elt i */ for (j = 0; j < table_len; j++) { @@ -213,10 +214,11 @@ __ec_node_subset_complete(struct ec_node **table, size_t table_len, continue; ret = ec_node_parse_child(table[i], parsed, strvec); + if (ret < 0) + goto fail; + if (ret == EC_PARSED_NOMATCH) continue; - else if (ret < 0) - goto fail; len = ret; childvec = ec_strvec_ndup(strvec, len, diff --git a/lib/ecoli_parsed.c b/lib/ecoli_parsed.c index e8f3cb9..d338953 100644 --- a/lib/ecoli_parsed.c +++ b/lib/ecoli_parsed.c @@ -39,6 +39,17 @@ #include #include +TAILQ_HEAD(ec_parsed_list, ec_parsed); + +struct ec_parsed { + TAILQ_ENTRY(ec_parsed) next; + struct ec_parsed_list children; + struct ec_parsed *parent; + const struct ec_node *node; + struct ec_strvec *strvec; + struct ec_keyval *attrs; +}; + int ec_node_parse_child(struct ec_node *node, struct ec_parsed *state, const struct ec_strvec *strvec) { @@ -65,16 +76,17 @@ int ec_node_parse_child(struct ec_node *node, struct ec_parsed *state, if (child == NULL) return -ENOMEM; - child->node = node; + ec_parsed_set_node(child, node); ec_parsed_add_child(state, child); ret = node->type->parse(node, child, strvec); + if (ret < 0) + return ret; + if (ret == EC_PARSED_NOMATCH) { ec_parsed_del_child(state, child); assert(TAILQ_EMPTY(&child->children)); ec_parsed_free(child); return ret; - } else if (ret < 0) { - return ret; } match_strvec = ec_strvec_ndup(strvec, 0, ret); @@ -97,10 +109,12 @@ struct ec_parsed *ec_node_parse_strvec(struct ec_node *node, return NULL; ret = ec_node_parse_child(node, parsed, strvec); - if (ret < 0 && ret != EC_PARSED_NOMATCH) { + if (ret < 0) { ec_parsed_free(parsed); - parsed = NULL; - } else if (ret != EC_PARSED_NOMATCH) { + return NULL; + } + + if (ret != EC_PARSED_NOMATCH) { /* remove dummy root node */ child_parsed = ec_parsed_get_last_child(parsed); ec_parsed_del_child(parsed, child_parsed); @@ -161,6 +175,45 @@ struct ec_parsed *ec_parsed(void) return NULL; } +struct ec_parsed *ec_parsed_dup(const struct ec_parsed *parsed) +{ + struct ec_parsed *dup = NULL; + struct ec_parsed *child, *dup_child; + struct ec_keyval *attrs = NULL; + + if (parsed == NULL) + return NULL; + + dup = ec_parsed(); + if (dup == NULL) + return NULL; + + attrs = ec_keyval_dup(parsed->attrs); + if (attrs == NULL) + goto fail; + ec_keyval_free(dup->attrs); + dup->attrs = attrs; + + if (parsed->strvec != NULL) { + dup->strvec = ec_strvec_dup(parsed->strvec); + if (dup->strvec == NULL) + goto fail; + } + + TAILQ_FOREACH(child, &parsed->children, next) { + dup_child = ec_parsed_dup(child); + if (dup_child == NULL) + goto fail; + ec_parsed_add_child(dup, dup_child); + } + + return dup; + +fail: + ec_parsed_free(dup); + return NULL; +} + void ec_parsed_free_children(struct ec_parsed *parsed) { struct ec_parsed *child; @@ -247,11 +300,37 @@ void ec_parsed_del_child(struct ec_parsed *parsed, // XXX rename del in unlink? } struct ec_parsed * -ec_parsed_get_last_child(struct ec_parsed *parsed) +ec_parsed_get_first_child(const struct ec_parsed *parsed) +{ + return TAILQ_FIRST(&parsed->children); +} + +struct ec_parsed * +ec_parsed_get_last_child(const struct ec_parsed *parsed) { return TAILQ_LAST(&parsed->children, ec_parsed_list); } +struct ec_parsed *ec_parsed_get_next(const struct ec_parsed *parsed) +{ + return TAILQ_NEXT(parsed, next); +} + +bool ec_parsed_has_child(const struct ec_parsed *parsed) +{ + return !TAILQ_EMPTY(&parsed->children); +} + +const struct ec_node *ec_parsed_get_node(const struct ec_parsed *parsed) +{ + return parsed->node; +} + +void ec_parsed_set_node(struct ec_parsed *parsed, const struct ec_node *node) +{ + parsed->node = node; +} + void ec_parsed_del_last_child(struct ec_parsed *parsed) // rename in free { struct ec_parsed *child; diff --git a/lib/ecoli_parsed.h b/lib/ecoli_parsed.h index 0755336..afb765e 100644 --- a/lib/ecoli_parsed.h +++ b/lib/ecoli_parsed.h @@ -25,6 +25,14 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/** + * 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_PARSED_ #define ECOLI_PARSED_ @@ -34,26 +42,42 @@ #include struct ec_node; +struct ec_parsed; -TAILQ_HEAD(ec_parsed_list, ec_parsed); - -/* - node == NULL + empty children list means "no match" - XXX still valid? -*/ -struct ec_parsed { - TAILQ_ENTRY(ec_parsed) next; - struct ec_parsed_list children; - struct ec_parsed *parent; - const struct ec_node *node; - struct ec_strvec *strvec; - struct ec_keyval *attrs; -}; - +/** + * Create an empty parse tree. + * + * @return + * The empty parse tree. + */ struct ec_parsed *ec_parsed(void); + +/** + * + * + * + */ void ec_parsed_free(struct ec_parsed *parsed); + +/** + * + * + * + */ void ec_parsed_free_children(struct ec_parsed *parsed); +/** + * + * + * + */ +struct ec_parsed *ec_parsed_dup(const struct ec_parsed *parsed); + +/** + * + * + * + */ const struct ec_strvec *ec_parsed_strvec(const struct ec_parsed *parsed); /* XXX we could use a cache to store possible completions or match: the @@ -63,12 +87,28 @@ const struct ec_strvec *ec_parsed_strvec(const struct ec_parsed *parsed); /* a NULL return value is an error, with errno set ENOTSUP: no ->parse() operation */ +/** + * + * + * + */ struct ec_parsed *ec_node_parse(struct ec_node *node, const char *str); +/** + * + * + * + */ struct ec_parsed *ec_node_parse_strvec(struct ec_node *node, const struct ec_strvec *strvec); -#define EC_PARSED_NOMATCH INT_MIN +/** + * + * + * + */ +#define EC_PARSED_NOMATCH INT_MAX + /* internal: used by nodes * * state is the current parse tree, which is built piece by piece while @@ -79,8 +119,7 @@ struct ec_parsed *ec_node_parse_strvec(struct ec_node *node, * possible descendants. * * return: - * XXX change EC_PARSED_NOMATCH to INT_MAX? - * EC_PARSED_NOMATCH (negative) if it does not match + * EC_PARSED_NOMATCH (positive) if it does not match * any other negative value (-errno) for other errors * the number of matched strings in strvec * XXX state is not freed on error ? @@ -89,23 +128,127 @@ int ec_node_parse_child(struct ec_node *node, struct ec_parsed *state, const struct ec_strvec *strvec); +/** + * + * + * + */ void ec_parsed_add_child(struct ec_parsed *parsed, struct ec_parsed *child); +/** + * + * + * + */ void ec_parsed_del_child(struct ec_parsed *parsed, struct ec_parsed *child); +/** + * + * + * + */ struct ec_parsed *ec_parsed_get_root(struct ec_parsed *parsed); + +/** + * + * + * + */ struct ec_parsed *ec_parsed_get_parent(struct ec_parsed *parsed); -struct ec_parsed *ec_parsed_get_last_child(struct ec_parsed *parsed); + +/** + * Get the first child of a tree. + * + */ +struct ec_parsed *ec_parsed_get_first_child(const struct ec_parsed *parsed); + +/** + * + * + * + */ +struct ec_parsed *ec_parsed_get_last_child(const struct ec_parsed *parsed); + +/** + * + * + * + */ +struct ec_parsed *ec_parsed_get_next(const struct ec_parsed *parsed); + +/** + * + * + * + */ +#define EC_PARSED_FOREACH_CHILD(child, parsed) \ + for (child = ec_parsed_get_first_child(parsed); \ + child != NULL; \ + child = ec_parsed_get_next(child)) \ + +/** + * + * + * + */ +bool ec_parsed_has_child(const struct ec_parsed *parsed); + +/** + * + * + * + */ +const struct ec_node *ec_parsed_get_node(const struct ec_parsed *parsed); + +/** + * + * + * + */ +void ec_parsed_set_node(struct ec_parsed *parsed, const struct ec_node *node); + +/** + * + * + * + */ void ec_parsed_del_last_child(struct ec_parsed *parsed); + +/** + * + * + * + */ int ec_parsed_get_path(struct ec_parsed *parsed, struct ec_node **path); +/** + * + * + * + */ void ec_parsed_dump(FILE *out, const struct ec_parsed *parsed); +/** + * + * + * + */ struct ec_parsed *ec_parsed_find_first(struct ec_parsed *parsed, const char *id); +/** + * + * + * + */ size_t ec_parsed_len(const struct ec_parsed *parsed); + +/** + * + * + * + */ size_t ec_parsed_matches(const struct ec_parsed *parsed); #endif diff --git a/lib/main-readline.c b/lib/main-readline.c index 5bbe18e..da447d8 100644 --- a/lib/main-readline.c +++ b/lib/main-readline.c @@ -126,21 +126,17 @@ static char **my_attempted_completion(const char *text, int start, int end) } /* this function builds the help string */ -static char *get_node_help(const struct ec_completed_node *compnode) +static char *get_node_help(const struct ec_completed_item *item) { -// const struct ec_completed_item *item; -// const struct ec_node *node; + const struct ec_node *node; char *help = NULL; const char *node_help = NULL; const char *node_desc = NULL; // size_t i; - (void)compnode; + (void)item; #if 0 //XXX - /* Since we display only one help per node, only look at the first item - * to get the path. The objective is to retrieve the most precise - * help for this node. */ - item = TAILQ_FIRST(&compnode->items); + /* Retrieve the most precise help for this node. */ for (i = 0; i < item->pathlen; i++) { node = item->path[i]; if (node_help == NULL) @@ -148,10 +144,14 @@ static char *get_node_help(const struct ec_completed_node *compnode) if (node_desc == NULL) node_desc = ec_node_desc(node); } +#else + node = ec_completed_item_get_node(item); + node_help = ec_keyval_get(ec_node_attrs(node), "help"); + node_desc = ec_node_desc(node); #endif if (node_help == NULL) - node_help = ""; + node_help = "-"; if (node_desc == NULL) return NULL; @@ -163,11 +163,13 @@ static char *get_node_help(const struct ec_completed_node *compnode) static int show_help(int ignore, int invoking_key) { - const struct ec_completed_node *compnode; + struct ec_completed_iter *iter; + const struct ec_completed_item *item; + const struct ec_node *prev_node = NULL; struct ec_completed *c; struct ec_parsed *p; - char *line; - unsigned int count, i; + char *line = NULL; + unsigned int count; char **helps = NULL; int match = 0; @@ -176,54 +178,74 @@ static int show_help(int ignore, int invoking_key) line = strdup(rl_line_buffer); if (line == NULL) - return 1; + goto fail; /* check if the current line matches */ p = ec_node_parse(commands, line); if (ec_parsed_matches(p)) match = 1; ec_parsed_free(p); + p = NULL; /* complete at current cursor position */ line[rl_point] = '\0'; c = ec_node_complete(commands, line); - //ec_completed_dump(stdout, c); free(line); + line = NULL; if (c == NULL) - return 1; + goto fail; + + //ec_completed_dump(stdout, c); /* let's display one contextual help per node */ count = 0; - TAILQ_FOREACH(compnode, &c->nodes, next) { - if (TAILQ_EMPTY(&compnode->items)) - continue; - count++; - } + iter = ec_completed_iter(c, EC_NO_MATCH | EC_MATCH | EC_PARTIAL_MATCH); + if (iter == NULL) + goto fail; - helps = calloc(count + match + 1, sizeof(char *)); + /* strangely, rl_display_match_list() expects first index at 1 */ + helps = calloc(match + 1, sizeof(char *)); if (helps == NULL) - return 1; - + goto fail; if (match) helps[1] = ""; - /* strangely, rl_display_match_list() expects first index at 1 */ - i = match + 1; - TAILQ_FOREACH(compnode, &c->nodes, next) { - if (TAILQ_EMPTY(&compnode->items)) + while ((item = ec_completed_iter_next(iter)) != NULL) { + const struct ec_node *node; + char **tmp; + + /* keep one help per node, skip other items */ + node = ec_completed_item_get_node(item); + if (node == prev_node) continue; - helps[i++] = get_node_help(compnode); + + tmp = realloc(helps, (count + match + 2) * sizeof(char *)); + if (tmp == NULL) + goto fail; + helps = tmp; + helps[count + match + 1] = get_node_help(item); + count++; } + ec_completed_iter_free(iter); ec_completed_free(c); - rl_display_match_list(helps, count + match, 1000); /* XXX 1000 */ - rl_forced_update_display(); return 0; - // free helps[n] XXX on error ? +fail: + ec_completed_iter_free(iter); + ec_parsed_free(p); + free(line); + ec_completed_free(c); + if (helps != NULL) { + while (count--) + free(helps[count + match + 1]); + } + free(helps); + + return 1; } static int create_commands(void) diff --git a/lib/todo.txt b/lib/todo.txt index 0001de1..58b7996 100644 --- a/lib/todo.txt +++ b/lib/todo.txt @@ -202,7 +202,7 @@ return values: - 0: success, child->strvec is set by node (NULL = no_match) - -1: error (errno is set) maybe complex to use: -- the node must set it (ex: "return ec_parsed_node_match()") +- the node must set the match (ex: "return ec_parsed_node_match()") - the caller must use accessor to check if it matches or not alternative idea for return values: @@ -210,6 +210,7 @@ alternative idea for return values: - -1: error (errno is set) - -2 or MAX_INT: success, but no match This is strange to have a specific value for no match +With MAX_INT, this is the best (less bad) alternative alternative idea for return values: - ec_parse_result_match(n_tokens >= 0) @@ -251,6 +252,11 @@ partial: beginning of a completion, does not match the token (good example is a directory in a path) unknown: could complete, but the node does not know how +struct completion_item { + const char *value; + const char *disp; +} + struct completed_elt { ec_parsed *parse_tree; // current tree state ec_node *last; // last node of the tree @@ -274,15 +280,15 @@ a node can filter the completions [] -> foo 3 str(foo) - seq - option - str(foo) <- + seq + option + str(foo) <- - ? 5 int(1,10) - seq - option - or - int <- + "" 5 int(1,10) + seq + option + or + int <- bar 6 str(bar) foo 7 str(bar) @@ -308,7 +314,6 @@ a node can filter the completions changes: - a completion item should contain a strvec for the value (the display string remains a string) -- use a INT_MIN or a specific - there is maybe no good reason to split in: - ec_completed_item() - ec_completed_item_set() -- 2.39.5