# HG changeset patch # User Mike Becker # Date 1758475890 -7200 # Node ID 748d0d40881ea6ce709452cdd88b3013f64cd80c # Parent 4ebb27ff6f7610dd247ce372bebe098be5672486 kv-list: implement mutating iterator support relates to #461 diff -r 4ebb27ff6f76 -r 748d0d40881e src/cx/iterator.h --- a/src/cx/iterator.h Sun Sep 21 18:42:18 2025 +0200 +++ b/src/cx/iterator.h Sun Sep 21 19:31:30 2025 +0200 @@ -70,6 +70,10 @@ */ void (*next)(void *); /** + * Original implementation in case the function needs to be wrapped. + */ + void (*next_impl)(void *); + /** * Indicates whether this iterator may remove elements. */ bool mutating; diff -r 4ebb27ff6f76 -r 748d0d40881e src/kv_list.c --- a/src/kv_list.c Sun Sep 21 18:42:18 2025 +0200 +++ b/src/kv_list.c Sun Sep 21 19:31:30 2025 +0200 @@ -246,14 +246,31 @@ kv_list->list_methods->reverse(list); } +static void cx_kvl_list_iter_next(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + // remove the assigned key from the map before calling the actual function + cx_kv_list *kv_list = iter->src_handle.m; + cx_kv_list_update_destructors(kv_list); + char *node = iter->elem_handle; + CxHashKey *key = cx_kv_list_loc_key(kv_list, node + kv_list->list.loc_data); + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + } + iter->base.next_impl(it); +} + static struct cx_iterator_s cx_kvl_iterator( const struct cx_list_s *list, size_t index, bool backward ) { const cx_kv_list *kv_list = (const cx_kv_list*)list; - // TODO: cannot really forward, because mutating iterators must be able to remove the element - return kv_list->list_methods->iterator(list, index, backward); + struct cx_iterator_s iter = kv_list->list_methods->iterator(list, index, backward); + iter.base.next_impl = iter.base.next; + iter.base.next = cx_kvl_list_iter_next; + return iter; } static void cx_kvl_map_deallocate(struct cx_map_s *map) { @@ -379,16 +396,37 @@ key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); if (key->hash != 0) break; } + + // remove previous element if requested + if (iter->base.remove) { + iter->base.remove = false; + cx_kv_list_update_destructors(kv_list); + char *elem = iter->elem; + char *elem_data = elem + kv_list->list.loc_data; + CxHashKey *elem_key = cx_kv_list_loc_key(kv_list, elem_data); + // key is guaranteed to exist because iterator only iterates over elems with a key + kv_list->map_methods->remove(&kv_list->map->map_base.base, *elem_key, NULL); + cx_invoke_advanced_destructor(&kv_list->list.base, elem_data); + cx_linked_list_remove( + &kv_list->list.begin, + &kv_list->list.end, + kv_list->list.loc_prev, + kv_list->list.loc_next, + elem + ); + cxFree(kv_list->list.base.collection.allocator, elem); + kv_list->list.base.collection.size--; + iter->index--; + iter->elem_count--; + } + + // advance to the next element, if any if (next == NULL) { iter->index = kv_list->list.base.collection.size; iter->elem = NULL; iter->entry = (CxMapEntry){NULL, NULL}; return; } - - // TODO: implement removal - - // advance to the next element iter->index++; iter->elem = next; iter->entry.key = key; diff -r 4ebb27ff6f76 -r 748d0d40881e tests/test_kv_list.c --- a/tests/test_kv_list.c Sun Sep 21 18:42:18 2025 +0200 +++ b/tests/test_kv_list.c Sun Sep 21 19:31:30 2025 +0200 @@ -856,16 +856,79 @@ // remove the first element (which was skipped anyway) and try again cxListRemove(cxKvListAsList(map), 0); - it = cxMapIterator(map); + it = cxMapIteratorKeys(map); CX_TEST_ASSERT(it.elem_count == 3); - CX_TEST_ASSERT(it.elem_size == sizeof(CxMapEntry)); + CX_TEST_ASSERT(it.elem_size == sizeof(CxHashKey)); CX_TEST_ASSERT(cxIteratorValid(it)); CX_TEST_ASSERT(it.index == 0); - entry = cxIteratorCurrent(it); - CX_TEST_ASSERT(*(int*)entry->value == 815); - CX_TEST_ASSERT(entry->key->len == 3); - CX_TEST_ASSERT(strncmp(entry->key->data, "xyz", 3) == 0); + CxHashKey *key = cxIteratorCurrent(it); + CX_TEST_ASSERT(key->len == 3); + CX_TEST_ASSERT(strncmp(key->data, "xyz", 3) == 0); + } + cxMapFree(map); +} + +CX_TEST(test_kv_list_map_iterator_remove) { + CxMap *map = cxKvListCreateAsMapSimple(CX_STORE_POINTERS); + int x, y, z; + CX_TEST_DO { + cxDefineDestructor(map, kv_list_test_destr); + x = 815; + cxMapPut(map, "xyz", &x); + y = 8016; + cxMapPut(map, "abcd", &y); + z = 80017; + cxMapPut(map, "efghi", &z); + + kv_list_test_destr_val = 0; + CxMapIterator iter = cxMapMutIteratorValues(map); + cx_foreach(int *, elem, iter) { + if (*elem == 8016) { + cxIteratorFlagRemoval(iter); + } + } + + CX_TEST_ASSERT(kv_list_test_destr_val == 8016); + CX_TEST_ASSERT(cxMapSize(map) == 2); + CX_TEST_ASSERT(cxMapGet(map, "abcd") == NULL); + CxList *list = cxKvListAsList(map); + CX_TEST_ASSERT(cxListSize(list) == 2); + CX_TEST_ASSERT(*(int*)cxListAt(list, 0) == 815); + CX_TEST_ASSERT(*(int*)cxListAt(list, 1) == 80017); + } + cxMapFree(map); +} + +CX_TEST(test_kv_list_list_iterator_remove) { + CxMap *map = cxKvListCreateAsMapSimple(sizeof(int)); + CX_TEST_DO { + cxDefineAdvancedDestructor(map, kv_list_test_destr2, (void*)0xf00); + int x; + x = 815; + cxMapPut(map, "xyz", &x); + x = 8016; + cxMapPut(map, "abcd", &x); + x = 80017; + cxMapPut(map, "efghi", &x); + + CxList *list = cxKvListAsList(map); + kv_list_test_destr2_val = 0; + kv_list_test_destr2_extra = NULL; + CxIterator iter = cxListMutIterator(list); + cx_foreach(int *, elem, iter) { + if (*elem == 8016) { + cxIteratorFlagRemoval(iter); + } + } + + CX_TEST_ASSERT(kv_list_test_destr2_val == 8016); + CX_TEST_ASSERT(kv_list_test_destr2_extra == (void*)0xf00); + CX_TEST_ASSERT(cxMapSize(map) == 2); + CX_TEST_ASSERT(cxMapGet(map, "abcd") == NULL); + CX_TEST_ASSERT(cxListSize(list) == 2); + CX_TEST_ASSERT(*(int*)cxMapGet(map, "xyz") == 815); + CX_TEST_ASSERT(*(int*)cxMapGet(map, "efghi") == 80017); } cxMapFree(map); } @@ -907,6 +970,8 @@ cx_test_register(suite, test_kv_list_map_clear_destr_in_map); cx_test_register(suite, test_kv_list_destr_ptr); cx_test_register(suite, test_kv_list_map_iterator); + cx_test_register(suite, test_kv_list_map_iterator_remove); + cx_test_register(suite, test_kv_list_list_iterator_remove); return suite; }