Add cmd delete prefix apis as well as trie helpers for it

This commit is contained in:
pancake 2026-07-20 23:41:02 +02:00 committed by pancake
parent c72fa9691a
commit 74aeb08beb
6 changed files with 424 additions and 67 deletions

View file

@ -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 **/

View file

@ -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);

View file

@ -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
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}