kv-list: implement mutating iterator support default tip

Sun, 21 Sep 2025 19:31:30 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 21 Sep 2025 19:31:30 +0200
changeset 1386
748d0d40881e
parent 1385
4ebb27ff6f76

kv-list: implement mutating iterator support

relates to #461

src/cx/iterator.h file | annotate | diff | comparison | revisions
src/kv_list.c file | annotate | diff | comparison | revisions
tests/test_kv_list.c file | annotate | diff | comparison | revisions
--- 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;
--- 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;
--- 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;
 }

mercurial