diff --git a/libr/core/cmd_api.c b/libr/core/cmd_api.c index 9682586592..d58135755c 100644 --- a/libr/core/cmd_api.c +++ b/libr/core/cmd_api.c @@ -375,6 +375,31 @@ R_API bool r_cmd_unregister(RCmd *cmd, const char *name) { return cmd_name_is_valid (key) && r_trie_delete (cmd->handlers, key); } +R_API size_t r_cmd_unregister_prefix(RCmd *cmd, const char *prefix) { + R_RETURN_VAL_IF_FAIL (cmd && prefix, 0); + return r_trie_delete_prefix (cmd->handlers, r_strs_from (prefix)); +} + +typedef struct { + RCmdForeachCb callback; + void *user; +} RCmdForeachContext; + +static bool cmd_foreach_handler(RStrs name, void *value, void *user) { + RCmdForeachContext *context = user; + (void)value; + return context->callback (name, context->user); +} + +R_API bool r_cmd_foreach_prefix(const RCmd *cmd, const char *prefix, RCmdForeachCb callback, void *user) { + R_RETURN_VAL_IF_FAIL (cmd && prefix && callback, false); + RCmdForeachContext context = { + .callback = callback, + .user = user + }; + return r_trie_foreach_prefix (cmd->handlers, r_strs_from (prefix), cmd_foreach_handler, &context); +} + R_API bool r_cmd_add(RCmd *c, const char *cmd, RCmdCb cb) { int idx = (ut8)cmd[0]; RCmdItem *item = c->cmds[idx]; @@ -392,58 +417,70 @@ R_API void r_cmd_del(RCmd *cmd, const char *command) { R_FREE (cmd->cmds[idx]); } +static int cmd_call_registered(RCmd *cmd, RStrs input, bool *handled) { + RStrs lookup = input; + RCmdContext context = { + .cmd = cmd, + .user = cmd->data + }; + while (!r_strs_empty (lookup)) { + size_t matched = 0; + RCmdHandler *handler = r_trie_find_longest_prefix (cmd->handlers, lookup, &matched); + if (!handler || !matched) { + break; + } + context.handler_user = handler->user; + RCmdResult result = handler->callback (&context, input); + if (result.action != R_CMD_ACTION_UNHANDLED) { + *handled = true; + return result.action == R_CMD_ACTION_QUIT? -2 + : result.action == R_CMD_ACTION_ABORT? -1 + : (int)R_CLAMP (result.status, (st64)ST32_MIN, (st64)ST32_MAX); + } + lookup.b = lookup.a + matched - 1; + } + return -1; +} + R_API int r_cmd_call(RCmd *cmd, const char *input) { - RCore *core = cmd->data; - struct r_cmd_item_t *c; - int ret = -1; - RListIter *iter; R_RETURN_VAL_IF_FAIL (cmd && input, -1); + RCore *core = cmd->data; if (!*input) { - if (cmd->nullcallback) { - ret = cmd->nullcallback (cmd->data); - } - } else { - char *nstr = NULL; - RCons *cons = core->cons; - RCmdAliasVal *v = r_cmd_alias_get (cmd, input); - if (v && v->is_data) { - char *v_str = r_cmd_alias_val_strdup (v); - r_cons_print (cons, v_str); - free (v_str); - return true; - } + return cmd->nullcallback? cmd->nullcallback (cmd->data): -1; + } + RCmdAliasVal *v = r_cmd_alias_get (cmd, input); + if (v && v->is_data) { + char *v_str = r_cmd_alias_val_strdup (v); + r_cons_print (core->cons, v_str); + free (v_str); + return true; + } + bool handled = false; + int ret = cmd_call_registered (cmd, r_strs_from (input), &handled); + if (handled) { + return ret; + } + RListIter *iter; + if (cmd->libstore) { RCorePluginSession *cps; r_list_foreach (cmd->libstore->plugins, iter, cps) { RCorePlugin *plugin = cps->plugin; if (plugin->call && plugin->call (cps, input)) { - free (nstr); return true; } } - if (!*input) { - free (nstr); - return -1; - } - c = cmd->cmds[((ut8)input[0]) & 0xff]; - if (c && c->callback) { - if (*input) { - ret = c->callback (cmd->data, input + 1); - } else { - ret = c->callback (cmd->data, ""); - } - } else { - ret = -1; - // Check for command suggestion in SDB - if (core && core->sdb) { - const char *suggestion = sdb_const_get (core->sdb, input, NULL); - if (suggestion) { - R_LOG_INFO ("%s", suggestion); - } - } - } - free (nstr); } - return ret; + RCmdItem *item = cmd->cmds[(ut8)input[0]]; + if (item && item->callback) { + return item->callback (cmd->data, input + 1); + } + if (core && core->sdb) { + const char *suggestion = sdb_const_get (core->sdb, input, NULL); + if (suggestion) { + R_LOG_INFO ("%s", suggestion); + } + } + return -1; } /** macro.c **/ diff --git a/libr/include/r_cmd.h b/libr/include/r_cmd.h index cc9dc88101..89f0e57b37 100644 --- a/libr/include/r_cmd.h +++ b/libr/include/r_cmd.h @@ -11,7 +11,7 @@ extern "C" { typedef struct r_core_t RCore; typedef struct r_libstore_t RLibStore; -typedef struct r_cmd_context_t RCmdContext; +typedef struct r_cmd_t RCmd; #define MACRO_LIMIT 1024 #define MACRO_LABELS 20 @@ -35,7 +35,14 @@ typedef struct r_cmd_result_t { ut64 value; } RCmdResult; +typedef struct r_cmd_context_t { + RCmd *cmd; + void *user; + void *handler_user; +} RCmdContext; + typedef RCmdResult (*RCmdCtxCb) (RCmdContext *ctx, RStrs input); +typedef bool (*RCmdForeachCb) (RStrs name, void *user); typedef struct r_cmd_macro_label_t { char name[80]; @@ -79,7 +86,7 @@ typedef struct r_cmd_alias_val_t { } RCmdAliasVal; -typedef struct r_cmd_t { +struct r_cmd_t { void *data; // maybe its user? RCmdNullCb nullcallback; RCmdItem *cmds[UT8_MAX]; @@ -90,7 +97,7 @@ typedef struct r_cmd_t { HtUP *ts_symbols_ht; // RCmdDesc *root_cmd_desc; RTrie *handlers; -} RCmd; +}; #ifdef R_API R_API RCmd *r_cmd_new(void *data); @@ -103,6 +110,10 @@ R_API bool r_cmd_add(RCmd *cmd, const char *command, RCmdCb callback); R_API bool r_cmd_register(RCmd *cmd, const char *name, RCmdCtxCb callback, void *handler_user); /* Removes only the exact registered name, preserving descendant handlers. */ R_API bool r_cmd_unregister(RCmd *cmd, const char *name); +/* Removes every handler whose name starts with prefix and returns their count. */ +R_API size_t r_cmd_unregister_prefix(RCmd *cmd, const char *prefix); +/* Visits matching names in lexical order; name is transient and false stops. */ +R_API bool r_cmd_foreach_prefix(const RCmd *cmd, const char *prefix, RCmdForeachCb callback, void *user); /* r_cmd_macro */ R_API RCmdMacroItem *r_cmd_macro_item_new(void); diff --git a/libr/include/r_util/r_trie.h b/libr/include/r_util/r_trie.h index e6ff6a6607..71f9f41b53 100644 --- a/libr/include/r_util/r_trie.h +++ b/libr/include/r_util/r_trie.h @@ -12,6 +12,7 @@ extern "C" { typedef struct r_trie_t RTrie; typedef void (*RTrieFree)(void *value); +typedef bool (*RTrieForeachCb)(RStrs key, void *value, void *user); /* Keys are copied and may contain NUL bytes. Values must be non-NULL. After a * successful insertion the trie owns the value; replacing a key releases its @@ -23,9 +24,14 @@ R_API size_t r_trie_size(const RTrie *trie); R_API bool r_trie_insert(RTrie *trie, RStrs key, void *value); R_API void *r_trie_find(const RTrie *trie, RStrs key); R_API void *r_trie_find_longest_prefix(const RTrie *trie, RStrs input, R_OUT size_t *matched_len); +/* Visits matching keys in lexical order; key is transient and false stops the walk. + * The callback must not mutate the trie. */ +R_API bool r_trie_foreach_prefix(const RTrie *trie, RStrs prefix, RTrieForeachCb callback, void *user); /* Removes a key and transfers ownership of its value to the caller. */ R_API void *r_trie_take(RTrie *trie, RStrs key); R_API bool r_trie_delete(RTrie *trie, RStrs key); +/* Removes prefix itself and every descendant key, returning their count. */ +R_API size_t r_trie_delete_prefix(RTrie *trie, RStrs prefix); #ifdef __cplusplus } diff --git a/libr/util/trie.c b/libr/util/trie.c index aecb968326..8dbf5691dd 100644 --- a/libr/util/trie.c +++ b/libr/util/trie.c @@ -5,9 +5,10 @@ typedef struct r_trie_node_t { ut8 *segment; - size_t segment_len; void *value; struct r_trie_node_t **children; + struct r_trie_node_t *parent; + size_t segment_len; size_t children_count; size_t children_capacity; } RTrieNode; @@ -18,6 +19,13 @@ struct r_trie_t { size_t size; }; +typedef struct { + char *bytes; + size_t length; + size_t capacity; + bool failed; +} RTrieKey; + static RTrieNode *trie_node_new(RStrs segment, void *value) { RTrieNode *node = R_NEW0 (RTrieNode); node->segment_len = r_strs_len (segment); @@ -39,27 +47,26 @@ static void trie_node_free_shallow(RTrieNode *node) { free (node); } -/* Iterative destruction: `segment` is repurposed as a parent link so that - * arbitrarily deep tries cannot exhaust the stack. */ -static void trie_node_free(RTrieNode *node, RTrieFree free_value) { - free (node->segment); - node->segment = NULL; - while (node) { +// Iterative destruction avoids exhausting the stack on deep tries. +static size_t trie_node_free(RTrieNode *node, RTrieFree free_value) { + RTrieNode *stop = node->parent; + size_t values = 0; + while (node != stop) { if (node->children_count) { - RTrieNode *child = node->children[--node->children_count]; - free (child->segment); - child->segment = (ut8 *)node; - node = child; + node = node->children[--node->children_count]; } else { - RTrieNode *parent = (RTrieNode *)node->segment; - if (node->value && free_value) { - free_value (node->value); + RTrieNode *parent = node->parent; + if (node->value) { + values++; + if (free_value) { + free_value (node->value); + } } - free (node->children); - free (node); + trie_node_free_shallow (node); node = parent; } } + return values; } static bool trie_node_find_child(const RTrieNode *node, ut8 first, size_t *index) { @@ -103,6 +110,7 @@ static bool trie_node_insert_child(RTrieNode *node, size_t index, RTrieNode *chi memmove (node->children + index + 1, node->children + index, (node->children_count - index) * sizeof (RTrieNode *)); node->children[index] = child; + child->parent = node; node->children_count++; return true; } @@ -117,6 +125,63 @@ static size_t trie_common_prefix(const RTrieNode *node, RStrs key) { return common; } +static bool trie_key_append(RTrieKey *key, const ut8 *bytes, size_t length) { + if (!length) { + return true; + } + if (length > SZT_MAX - key->length) { + return false; + } + size_t needed = key->length + length; + if (needed > key->capacity) { + size_t capacity = key->capacity? key->capacity: 64; + while (capacity < needed) { + if (capacity > SZT_MAX / 2) { + capacity = needed; + break; + } + capacity *= 2; + } + char *resized = realloc (key->bytes, capacity); + if (!resized) { + return false; + } + key->bytes = resized; + key->capacity = capacity; + } + memcpy (key->bytes + key->length, bytes, length); + key->length = needed; + return true; +} + +static RTrieNode *trie_find_prefix_node(const RTrie *trie, RStrs prefix, RTrieKey *key) { + RTrieNode *node = trie->root; + size_t prefix_len = r_strs_len (prefix); + size_t offset = 0; + while (offset < prefix_len) { + size_t index; + if (!trie_node_find_child (node, (ut8)prefix.a[offset], &index)) { + return NULL; + } + RTrieNode *child = node->children[index]; + size_t remaining = prefix_len - offset; + size_t common = R_MIN (child->segment_len, remaining); + if (memcmp (child->segment, prefix.a + offset, common)) { + return NULL; + } + if (key && !trie_key_append (key, child->segment, child->segment_len)) { + key->failed = true; + return NULL; + } + node = child; + if (remaining <= child->segment_len) { + return node; + } + offset += child->segment_len; + } + return node; +} + R_API RTrie *r_trie_new(RTrieFree free_value) { RTrie *trie = R_NEW0 (RTrie); RTrieNode *root = R_NEW0 (RTrieNode); @@ -198,7 +263,12 @@ R_API bool r_trie_insert(RTrie *trie, RStrs key, void *value) { middle->children[1] = leaf; } } + child->parent = middle; + if (leaf) { + leaf->parent = middle; + } middle->children_count = middle->children_capacity; + middle->parent = node; node->children[index] = middle; trie->size++; return true; @@ -244,6 +314,52 @@ R_API void *r_trie_find(const RTrie *trie, RStrs key) { return (matched == r_strs_len (key))? value: NULL; } +R_API bool r_trie_foreach_prefix(const RTrie *trie, RStrs prefix, RTrieForeachCb callback, void *user) { + R_RETURN_VAL_IF_FAIL (trie && prefix.a && prefix.b >= prefix.a && callback, false); + RTrieKey key = { 0 }; + RTrieNode *node = trie_find_prefix_node (trie, prefix, &key); + if (!node) { + free (key.bytes); + return !key.failed; + } + RTrieNode *root = node; + for (;;) { + const char *bytes = key.bytes? key.bytes: ""; + if (node->value && !callback (r_strs_from_len (bytes, key.length), node->value, user)) { + free (key.bytes); + return false; + } + if (node->children_count) { + node = node->children[0]; + if (!trie_key_append (&key, node->segment, node->segment_len)) { + free (key.bytes); + return false; + } + continue; + } + while (node != root) { + RTrieNode *parent = node->parent; + size_t index; + trie_node_find_child (parent, node->segment[0], &index); + key.length -= node->segment_len; + if (++index < parent->children_count) { + node = parent->children[index]; + if (!trie_key_append (&key, node->segment, node->segment_len)) { + free (key.bytes); + return false; + } + break; + } + node = parent; + } + if (node == root) { + break; + } + } + free (key.bytes); + return true; +} + static void trie_node_compact_child(RTrieNode *node, size_t index) { RTrieNode *child = node->children[index]; if (child->value || child->children_count > 1) { @@ -270,6 +386,10 @@ static void trie_node_compact_child(RTrieNode *node, size_t index) { child->children = grandchild->children; child->children_count = grandchild->children_count; child->children_capacity = grandchild->children_capacity; + size_t i; + for (i = 0; i < child->children_count; i++) { + child->children[i]->parent = child; + } free (grandchild->segment); free (grandchild); } @@ -324,3 +444,33 @@ R_API bool r_trie_delete(RTrie *trie, RStrs key) { } return true; } + +R_API size_t r_trie_delete_prefix(RTrie *trie, RStrs prefix) { + R_RETURN_VAL_IF_FAIL (trie && prefix.a && prefix.b >= prefix.a, 0); + if (r_strs_empty (prefix)) { + RTrieNode *root = R_NEW0 (RTrieNode); + size_t removed = trie_node_free (trie->root, trie->free_value); + trie->root = root; + trie->size = 0; + return removed; + } + RTrieNode *node = trie_find_prefix_node (trie, prefix, NULL); + if (!node) { + return 0; + } + RTrieNode *parent = node->parent; + size_t index; + trie_node_find_child (parent, node->segment[0], &index); + memmove (parent->children + index, parent->children + index + 1, + (--parent->children_count - index) * sizeof (RTrieNode *)); + size_t removed = trie_node_free (node, trie->free_value); + trie->size -= removed; + node = parent; + while (node->parent) { + parent = node->parent; + trie_node_find_child (parent, node->segment[0], &index); + trie_node_compact_child (parent, index); + node = parent; + } + return removed; +} diff --git a/test/unit/test_cmd_api.c b/test/unit/test_cmd_api.c index cc164cb4a6..a1b71ea20e 100644 --- a/test/unit/test_cmd_api.c +++ b/test/unit/test_cmd_api.c @@ -8,19 +8,52 @@ static RCmdResult first_handler(RCmdContext *ctx, RStrs input) { return result; } -static RCmdResult second_handler(RCmdContext *ctx, RStrs input) { - (void)ctx; - (void)input; - RCmdResult result = { 0 }; +typedef struct { + size_t stop_after; + RStrBuf names; +} CmdVisit; + +static bool visit_command(RStrs name, void *user) { + CmdVisit *visit = user; + return r_strbuf_append_n (&visit->names, name.a, r_strs_len (name)) + && r_strbuf_append (&visit->names, ",") + && (!visit->stop_after || --visit->stop_after); +} + +typedef struct { + void *expected_user; + const char *expected_input; + RCmdAction action; + st64 status; + int calls; + int legacy_calls; + bool context_ok; +} DispatchState; + +static RCmdResult dispatch_handler(RCmdContext *ctx, RStrs input) { + DispatchState *state = ctx->handler_user; + state->calls++; + state->context_ok = ctx->cmd && ctx->user == state->expected_user + && r_strs_equals_str (input, state->expected_input); + RCmdResult result = { + .action = state->action, + .status = state->status + }; return result; } +static int legacy_handler(void *user, const char *input) { + DispatchState *state = user; + state->legacy_calls++; + return !strcmp (input, "fl?")? 9: -1; +} + static bool test_r_cmd_register(void) { RCmd *cmd = r_cmd_new (NULL); mu_assert_true (r_cmd_register (cmd, "af", first_handler, cmd), "register af"); - mu_assert_true (r_cmd_register (cmd, "afl", second_handler, NULL), "register afl"); + mu_assert_true (r_cmd_register (cmd, "afl", first_handler, NULL), "register afl"); mu_assert_eq (r_trie_size (cmd->handlers), 2, "registered handler count"); - mu_assert_false (r_cmd_register (cmd, "af", second_handler, NULL), "reject duplicate name"); + mu_assert_false (r_cmd_register (cmd, "af", first_handler, NULL), "reject duplicate name"); mu_assert_eq (r_trie_size (cmd->handlers), 2, "duplicate keeps handler count"); mu_assert_false (r_cmd_register (cmd, "", first_handler, NULL), "reject empty name"); mu_assert_false (r_cmd_register (cmd, "af l", first_handler, NULL), "reject whitespace"); @@ -34,7 +67,7 @@ static bool test_r_cmd_register(void) { static bool test_r_cmd_unregister(void) { RCmd *cmd = r_cmd_new (NULL); mu_assert_true (r_cmd_register (cmd, "af", first_handler, NULL), "register af"); - mu_assert_true (r_cmd_register (cmd, "afl", second_handler, NULL), "register afl"); + mu_assert_true (r_cmd_register (cmd, "afl", first_handler, NULL), "register afl"); mu_assert_true (r_cmd_unregister (cmd, "af"), "unregister exact name"); mu_assert_eq (r_trie_size (cmd->handlers), 1, "exact removal keeps descendant"); mu_assert_null (r_trie_find (cmd->handlers, R_STRS_LIT ("af")), "af removed"); @@ -47,9 +80,62 @@ static bool test_r_cmd_unregister(void) { mu_end; } +static bool test_r_cmd_prefix_registry(void) { + RCmd *cmd = r_cmd_new (NULL); + mu_assert_true (r_cmd_register (cmd, "af", first_handler, NULL), "register af"); + mu_assert_true (r_cmd_register (cmd, "afl", first_handler, NULL), "register afl"); + mu_assert_true (r_cmd_register (cmd, "aflj", first_handler, NULL), "register aflj"); + mu_assert_true (r_cmd_register (cmd, "agn", first_handler, NULL), "register agn"); + mu_assert_true (r_cmd_register (cmd, "pd", first_handler, NULL), "register pd"); + CmdVisit visit = { 0 }; + r_strbuf_init (&visit.names); + mu_assert_true (r_cmd_foreach_prefix (cmd, "af", visit_command, &visit), "enumerate af handlers"); + mu_assert_streq (r_strbuf_get (&visit.names), "af,afl,aflj,", "handler names are ordered"); + mu_assert_eq (r_cmd_unregister_prefix (cmd, "afl"), 2, "unregister handler subtree"); + mu_assert_notnull (r_trie_find (cmd->handlers, R_STRS_LIT ("af")), "parent handler remains"); + mu_assert_null (r_trie_find (cmd->handlers, R_STRS_LIT ("afl")), "subtree handler removed"); + mu_assert_eq (r_cmd_unregister_prefix (cmd, "missing"), 0, "unregister missing prefix"); + mu_assert_eq (r_cmd_unregister_prefix (cmd, ""), 3, "empty prefix unregisters all handlers"); + mu_assert_eq (r_trie_size (cmd->handlers), 0, "handler registry empty"); + r_strbuf_fini (&visit.names); + r_cmd_free (cmd); + mu_end; +} + +static bool test_r_cmd_registry_dispatch(void) { + DispatchState parent = { + .expected_input = "afl?", + .action = R_CMD_ACTION_CONTINUE, + .status = 7 + }; + DispatchState child = { + .expected_input = "afl?", + .action = R_CMD_ACTION_UNHANDLED + }; + parent.expected_user = child.expected_user = &child; + RCmd *cmd = r_cmd_new (&child); + mu_assert_true (r_cmd_register (cmd, "a", dispatch_handler, &parent), "register parent handler"); + mu_assert_true (r_cmd_register (cmd, "af", dispatch_handler, &child), "register child handler"); + mu_assert_eq (r_cmd_call (cmd, "afl?"), 7, "parent handles child fallback"); + mu_assert_eq (child.calls, 1, "longest prefix called first"); + mu_assert_eq (parent.calls, 1, "parent prefix called after unhandled"); + mu_assert_true (child.context_ok && parent.context_ok, "handlers receive context and full input"); + mu_assert_true (r_cmd_unregister (cmd, "a"), "remove registered parent"); + mu_assert_true (r_cmd_add (cmd, "a", legacy_handler), "register legacy fallback"); + mu_assert_eq (r_cmd_call (cmd, "afl?"), 9, "unhandled registry falls back to legacy"); + mu_assert_eq (child.legacy_calls, 1, "legacy fallback called once"); + child.action = R_CMD_ACTION_QUIT; + mu_assert_eq (r_cmd_call (cmd, "afl?"), -2, "quit action maps to legacy quit code"); + mu_assert_eq (child.legacy_calls, 1, "handled registry skips legacy callback"); + r_cmd_free (cmd); + mu_end; +} + static int all_tests(void) { mu_run_test (test_r_cmd_register); mu_run_test (test_r_cmd_unregister); + mu_run_test (test_r_cmd_prefix_registry); + mu_run_test (test_r_cmd_registry_dispatch); return tests_passed != tests_run; } diff --git a/test/unit/test_trie.c b/test/unit/test_trie.c index 8b175a3106..f7f3fa9a3c 100644 --- a/test/unit/test_trie.c +++ b/test/unit/test_trie.c @@ -20,6 +20,19 @@ static bool insert_string(RTrie *trie, const char *key, const char *value) { return true; } +typedef struct { + size_t stop_after; + RStrBuf names; +} TrieVisit; + +static bool visit_key(RStrs key, void *value, void *user) { + TrieVisit *visit = user; + (void)value; + return r_strbuf_append_n (&visit->names, key.a, r_strs_len (key)) + && r_strbuf_append (&visit->names, ",") + && (!visit->stop_after || --visit->stop_after); +} + static bool test_r_trie_find(void) { RTrie *trie = r_trie_new (free); mu_assert_true (insert_string (trie, "a", "a"), "insert a"); @@ -121,6 +134,58 @@ static bool test_r_trie_compact_chain(void) { mu_end; } +static bool test_r_trie_foreach_prefix(void) { + RTrie *trie = r_trie_new (free); + insert_string (trie, "", "root"); + insert_string (trie, "af", "af"); + insert_string (trie, "afl", "afl"); + insert_string (trie, "aflj", "aflj"); + insert_string (trie, "agn", "agn"); + insert_string (trie, "foobar", "foobar"); + insert_string (trie, "food", "food"); + TrieVisit visit = { 0 }; + r_strbuf_init (&visit.names); + mu_assert_true (r_trie_foreach_prefix (trie, R_STRS_LIT ("af"), visit_key, &visit), "walk af prefix"); + mu_assert_streq (r_strbuf_get (&visit.names), "af,afl,aflj,", "af names are ordered"); + r_strbuf_set (&visit.names, ""); + mu_assert_true (r_trie_foreach_prefix (trie, R_STRS_LIT ("fo"), visit_key, &visit), "walk inside an edge"); + mu_assert_streq (r_strbuf_get (&visit.names), "foobar,food,", "foo names are ordered"); + r_strbuf_set (&visit.names, ""); + visit.stop_after = 2; + mu_assert_false (r_trie_foreach_prefix (trie, R_STRS_LIT (""), visit_key, &visit), "callback stops walk"); + mu_assert_streq (r_strbuf_get (&visit.names), ",af,", "stopped names are ordered"); + r_strbuf_set (&visit.names, ""); + mu_assert_true (r_trie_foreach_prefix (trie, R_STRS_LIT ("missing"), visit_key, &visit), "missing prefix is empty"); + mu_assert_true (r_strbuf_is_empty (&visit.names), "missing prefix visits nothing"); + r_strbuf_fini (&visit.names); + r_trie_free (trie); + mu_end; +} + +static bool test_r_trie_delete_prefix(void) { + freed_values = 0; + RTrie *trie = r_trie_new (count_free); + insert_string (trie, "", "root"); + insert_string (trie, "af", "af"); + insert_string (trie, "afl", "afl"); + insert_string (trie, "aflj", "aflj"); + insert_string (trie, "agn", "agn"); + insert_string (trie, "foobar", "foobar"); + insert_string (trie, "food", "food"); + mu_assert_eq (r_trie_delete_prefix (trie, R_STRS_LIT ("afl")), 2, "delete exact prefix subtree"); + mu_assert_eq (r_trie_size (trie), 5, "prefix deletion updates size"); + mu_assert_streq (r_trie_find (trie, R_STRS_LIT ("af")), "af", "prefix parent remains"); + mu_assert_streq (r_trie_find (trie, R_STRS_LIT ("agn")), "agn", "prefix sibling remains"); + mu_assert_eq (r_trie_delete_prefix (trie, R_STRS_LIT ("fo")), 2, "delete prefix ending inside an edge"); + mu_assert_null (r_trie_find (trie, R_STRS_LIT ("foobar")), "interior prefix descendant removed"); + mu_assert_eq (r_trie_delete_prefix (trie, R_STRS_LIT ("")), 3, "empty prefix clears trie"); + mu_assert_eq (r_trie_size (trie), 0, "trie empty after clear"); + mu_assert_true (insert_string (trie, "new", "new"), "insert after clear"); + r_trie_free (trie); + mu_assert_eq (freed_values, 8, "all subtree values freed once"); + mu_end; +} + static int all_tests(void) { mu_run_test (test_r_trie_find); mu_run_test (test_r_trie_longest_prefix); @@ -128,6 +193,8 @@ static int all_tests(void) { mu_run_test (test_r_trie_replace_take_delete); mu_run_test (test_r_trie_empty_and_binary_keys); mu_run_test (test_r_trie_compact_chain); + mu_run_test (test_r_trie_foreach_prefix); + mu_run_test (test_r_trie_delete_prefix); return tests_passed != tests_run; }