mirror of
https://github.com/radareorg/radare2
synced 2026-08-22 20:23:32 -04:00
Add cmd delete prefix apis as well as trie helpers for it
This commit is contained in:
parent
c72fa9691a
commit
74aeb08beb
6 changed files with 424 additions and 67 deletions
|
|
@ -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 **/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
182
libr/util/trie.c
182
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue