| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. | ||
| 3 | * | ||
| 4 | * Copyright 2025 Mike Becker, Olaf Wintermann All rights reserved. | ||
| 5 | * | ||
| 6 | * Redistribution and use in source and binary forms, with or without | ||
| 7 | * modification, are permitted provided that the following conditions are met: | ||
| 8 | * | ||
| 9 | * 1. Redistributions of source code must retain the above copyright | ||
| 10 | * notice, this list of conditions and the following disclaimer. | ||
| 11 | * | ||
| 12 | * 2. Redistributions in binary form must reproduce the above copyright | ||
| 13 | * notice, this list of conditions and the following disclaimer in the | ||
| 14 | * documentation and/or other materials provided with the distribution. | ||
| 15 | * | ||
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
| 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
| 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
| 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
| 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
| 26 | * POSSIBILITY OF SUCH DAMAGE. | ||
| 27 | */ | ||
| 28 | |||
| 29 | #include "cx/kv_list.h" | ||
| 30 | #include "cx/hash_map.h" | ||
| 31 | #include "cx/linked_list.h" | ||
| 32 | |||
| 33 | #include <string.h> | ||
| 34 | #include <assert.h> | ||
| 35 | |||
| 36 | typedef struct cx_kv_list_s cx_kv_list; | ||
| 37 | |||
| 38 | struct cx_kv_list_map_s { | ||
| 39 | struct cx_hash_map_s map_base; | ||
| 40 | /** Back-reference to the list. */ | ||
| 41 | cx_kv_list *list; | ||
| 42 | }; | ||
| 43 | |||
| 44 | struct cx_kv_list_s { | ||
| 45 | struct cx_linked_list_s list; | ||
| 46 | /** The lookup map - stores pointers to the nodes. */ | ||
| 47 | struct cx_kv_list_map_s *map; | ||
| 48 | const cx_list_class *list_methods; | ||
| 49 | const cx_map_class *map_methods; | ||
| 50 | cx_destructor_func list_destr; | ||
| 51 | cx_destructor_func2 list_destr2; | ||
| 52 | void *list_destr_data; | ||
| 53 | cx_destructor_func map_destr; | ||
| 54 | cx_destructor_func2 map_destr2; | ||
| 55 | void *map_destr_data; | ||
| 56 | }; | ||
| 57 | |||
| 58 | 9425 | static void cx_kv_list_destructor_wrapper(void *list_ptr, void *elem) { | |
| 59 | 9425 | const cx_kv_list *kv_list = list_ptr; | |
| 60 | // list destructor is already called with proper deref of the elem | ||
| 61 |
2/2✓ Branch 0 (2→3) taken 164 times.
✓ Branch 1 (2→4) taken 9261 times.
|
9425 | if (kv_list->list_destr) { |
| 62 | 164 | kv_list->list_destr(elem); | |
| 63 | } | ||
| 64 |
2/2✓ Branch 0 (4→5) taken 152 times.
✓ Branch 1 (4→6) taken 9273 times.
|
9425 | if (kv_list->list_destr2) { |
| 65 | 152 | kv_list->list_destr2(kv_list->list_destr_data, elem); | |
| 66 | } | ||
| 67 |
2/2✓ Branch 0 (6→7) taken 851 times.
✓ Branch 1 (6→8) taken 8574 times.
|
9425 | if (kv_list->map_destr) { |
| 68 | 851 | kv_list->map_destr(elem); | |
| 69 | } | ||
| 70 |
2/2✓ Branch 0 (8→9) taken 7 times.
✓ Branch 1 (8→10) taken 9418 times.
|
9425 | if (kv_list->map_destr2) { |
| 71 | 7 | kv_list->map_destr2(kv_list->map_destr_data, elem); | |
| 72 | } | ||
| 73 | 9425 | } | |
| 74 | |||
| 75 | 531 | static void cx_kv_list_update_destructors(cx_kv_list *list) { | |
| 76 | // we copy the destructors to our custom fields and register | ||
| 77 | // an own destructor function which invokes all these | ||
| 78 |
2/2✓ Branch 0 (2→3) taken 8 times.
✓ Branch 1 (2→4) taken 523 times.
|
531 | if (list->list.base.collection.simple_destructor != NULL) { |
| 79 | 8 | list->list_destr = list->list.base.collection.simple_destructor; | |
| 80 | 8 | list->list.base.collection.simple_destructor = NULL; | |
| 81 | } | ||
| 82 |
2/2✓ Branch 0 (4→5) taken 278 times.
✓ Branch 1 (4→6) taken 253 times.
|
531 | if (list->list.base.collection.advanced_destructor != cx_kv_list_destructor_wrapper) { |
| 83 | 278 | list->list_destr2 = list->list.base.collection.advanced_destructor; | |
| 84 | 278 | list->list_destr_data = list->list.base.collection.destructor_data; | |
| 85 | 278 | list->list.base.collection.advanced_destructor = cx_kv_list_destructor_wrapper; | |
| 86 | 278 | list->list.base.collection.destructor_data = list; | |
| 87 | } | ||
| 88 |
2/2✓ Branch 0 (6→7) taken 160 times.
✓ Branch 1 (6→8) taken 371 times.
|
531 | if (list->map->map_base.base.collection.simple_destructor != NULL) { |
| 89 | 160 | list->map_destr = list->map->map_base.base.collection.simple_destructor; | |
| 90 | 160 | list->map->map_base.base.collection.simple_destructor = NULL; | |
| 91 | } | ||
| 92 |
2/2✓ Branch 0 (8→9) taken 4 times.
✓ Branch 1 (8→10) taken 527 times.
|
531 | if (list->map->map_base.base.collection.advanced_destructor != NULL) { |
| 93 | 4 | list->map_destr2 = list->map->map_base.base.collection.advanced_destructor; | |
| 94 | 4 | list->map_destr_data = list->map->map_base.base.collection.destructor_data; | |
| 95 | 4 | list->map->map_base.base.collection.advanced_destructor = NULL; | |
| 96 | 4 | list->map->map_base.base.collection.destructor_data = NULL; | |
| 97 | } | ||
| 98 | 531 | } | |
| 99 | |||
| 100 | 1744 | static CxHashKey *cx_kv_list_loc_key(cx_kv_list *list, void *node_data) { | |
| 101 | 1744 | return (CxHashKey*)((char*)node_data - list->list.loc_data + list->list.loc_extra); | |
| 102 | } | ||
| 103 | |||
| 104 | 113 | static void cx_kvl_deallocate(struct cx_list_s *list) { | |
| 105 | 113 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 106 | // patch the destructors | ||
| 107 | 113 | cx_kv_list_update_destructors(kv_list); | |
| 108 | 113 | kv_list->map_methods->deallocate(&kv_list->map->map_base.base); | |
| 109 | // then free the list, now the destructors may be called | ||
| 110 | 113 | kv_list->list_methods->deallocate(list); | |
| 111 | 113 | } | |
| 112 | |||
| 113 | 4617 | static void *cx_kvl_insert_element( | |
| 114 | struct cx_list_s *list, | ||
| 115 | size_t index, | ||
| 116 | const void *data | ||
| 117 | ) { | ||
| 118 | 4617 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 119 | 4617 | return kv_list->list_methods->insert_element(list, index, data); | |
| 120 | } | ||
| 121 | |||
| 122 | 34 | static size_t cx_kvl_insert_array( | |
| 123 | struct cx_list_s *list, | ||
| 124 | size_t index, | ||
| 125 | const void *data, | ||
| 126 | size_t n | ||
| 127 | ) { | ||
| 128 | 34 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 129 | 34 | return kv_list->list_methods->insert_array(list, index, data, n); | |
| 130 | } | ||
| 131 | |||
| 132 | 20 | static size_t cx_kvl_insert_sorted( | |
| 133 | struct cx_list_s *list, | ||
| 134 | const void *sorted_data, | ||
| 135 | size_t n | ||
| 136 | ) { | ||
| 137 | 20 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 138 | 20 | return kv_list->list_methods->insert_sorted(list, sorted_data, n); | |
| 139 | } | ||
| 140 | |||
| 141 | 20 | static size_t cx_kvl_insert_unique( | |
| 142 | struct cx_list_s *list, | ||
| 143 | const void *sorted_data, | ||
| 144 | size_t n | ||
| 145 | ) { | ||
| 146 | 20 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 147 | 20 | return kv_list->list_methods->insert_unique(list, sorted_data, n); | |
| 148 | } | ||
| 149 | |||
| 150 | 10 | static int cx_kvl_insert_iter( | |
| 151 | struct cx_iterator_s *iter, | ||
| 152 | const void *elem, | ||
| 153 | int prepend | ||
| 154 | ) { | ||
| 155 | 10 | cx_kv_list *kv_list = iter->src_handle; | |
| 156 | 10 | return kv_list->list_methods->insert_iter(iter, elem, prepend); | |
| 157 | } | ||
| 158 | |||
| 159 | 58 | static size_t cx_kvl_remove( | |
| 160 | struct cx_list_s *list, | ||
| 161 | size_t index, | ||
| 162 | size_t num, | ||
| 163 | void *targetbuf | ||
| 164 | ) { | ||
| 165 | 58 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 166 | // patch the destructors | ||
| 167 | // we also have to do that when targetbuf is NULL, | ||
| 168 | // because we do not want wrong destructors to be called when we remove keys from the map | ||
| 169 | 58 | cx_kv_list_update_destructors(kv_list); | |
| 170 | // iterate through the elements first and remove their keys from the map | ||
| 171 | 58 | CxIterator iter = kv_list->list_methods->iterator(list, index, false); | |
| 172 |
4/4✓ Branch 0 (11→12) taken 96 times.
✓ Branch 1 (11→14) taken 44 times.
✓ Branch 2 (13→5) taken 82 times.
✓ Branch 3 (13→14) taken 14 times.
|
140 | for (size_t i = 0; i < num && cxIteratorValid(iter); i++) { |
| 173 | 82 | void *node_data = cxIteratorCurrent(iter); | |
| 174 | 82 | CxHashKey *key = cx_kv_list_loc_key(kv_list, node_data); | |
| 175 | // when the hash is zero, there is no key assigned to that element | ||
| 176 |
2/2✓ Branch 0 (7→8) taken 15 times.
✓ Branch 1 (7→9) taken 67 times.
|
82 | if (key->hash != 0) { |
| 177 | 15 | kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); | |
| 178 | } | ||
| 179 | 82 | cxIteratorNext(iter); | |
| 180 | } | ||
| 181 | 58 | return kv_list->list_methods->remove(list, index, num, targetbuf); | |
| 182 | } | ||
| 183 | |||
| 184 | 10 | static void cx_kvl_clear(struct cx_list_s *list) { | |
| 185 | 10 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 186 | // patch the destructors | ||
| 187 | 10 | cx_kv_list_update_destructors(kv_list); | |
| 188 | // clear the list | ||
| 189 | 10 | kv_list->list_methods->clear(list); | |
| 190 | // then clear the map | ||
| 191 | 10 | kv_list->map_methods->clear(&kv_list->map->map_base.base); | |
| 192 | 10 | } | |
| 193 | |||
| 194 | 22 | static int cx_kvl_swap( | |
| 195 | struct cx_list_s *list, | ||
| 196 | size_t i, | ||
| 197 | size_t j | ||
| 198 | ) { | ||
| 199 | 22 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 200 | 22 | return kv_list->list_methods->swap(list, i, j); | |
| 201 | } | ||
| 202 | |||
| 203 | 6905 | static void *cx_kvl_at( | |
| 204 | const struct cx_list_s *list, | ||
| 205 | size_t index | ||
| 206 | ) { | ||
| 207 | 6905 | const cx_kv_list *kv_list = (const cx_kv_list*)list; | |
| 208 | 6905 | return kv_list->list_methods->at(list, index); | |
| 209 | } | ||
| 210 | |||
| 211 | 120 | static size_t cx_kvl_find_remove( | |
| 212 | struct cx_list_s *list, | ||
| 213 | const void *elem, | ||
| 214 | bool remove | ||
| 215 | ) { | ||
| 216 | 120 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 217 | // we do not use the original list methods, | ||
| 218 | // because that would need two passes for removal | ||
| 219 | // (the first to find the index, the second to get a pointer) | ||
| 220 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 119 times.
|
120 | if (list->collection.size == 0) return 0; |
| 221 | |||
| 222 | size_t index; | ||
| 223 | 119 | cx_linked_list *ll = &kv_list->list; | |
| 224 | 119 | char *node = cx_linked_list_find_c( | |
| 225 | 119 | ll->begin, | |
| 226 | 119 | ll->loc_next, ll->loc_data, | |
| 227 | elem, &index, | ||
| 228 | cx_list_compare_wrapper, | ||
| 229 | list | ||
| 230 | ); | ||
| 231 |
2/2✓ Branch 0 (5→6) taken 41 times.
✓ Branch 1 (5→7) taken 78 times.
|
119 | if (node == NULL) { |
| 232 | 41 | return list->collection.size; | |
| 233 | } | ||
| 234 |
2/2✓ Branch 0 (7→8) taken 10 times.
✓ Branch 1 (7→18) taken 68 times.
|
78 | if (remove) { |
| 235 | 10 | cx_kv_list_update_destructors(kv_list); | |
| 236 |
2/2✓ Branch 0 (9→10) taken 3 times.
✓ Branch 1 (9→11) taken 7 times.
|
10 | cx_invoke_advanced_destructor(list, node + ll->loc_data); |
| 237 | 10 | cx_linked_list_remove(&ll->begin, &ll->end, | |
| 238 | 10 | ll->loc_prev, ll->loc_next, node); | |
| 239 | 10 | CxHashKey *key = cx_kv_list_loc_key(kv_list, node + ll->loc_data); | |
| 240 |
2/2✓ Branch 0 (15→16) taken 4 times.
✓ Branch 1 (15→17) taken 6 times.
|
10 | if (key->hash != 0) { |
| 241 | 4 | kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); | |
| 242 | } | ||
| 243 | 10 | list->collection.size--; | |
| 244 | 10 | cxFree(list->collection.allocator, node); | |
| 245 | } | ||
| 246 | 78 | return index; | |
| 247 | } | ||
| 248 | |||
| 249 | 4 | static void cx_kvl_sort(struct cx_list_s *list) { | |
| 250 | 4 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 251 | 4 | kv_list->list_methods->sort(list); | |
| 252 | 4 | } | |
| 253 | |||
| 254 | 2 | static void cx_kvl_reverse(struct cx_list_s *list) { | |
| 255 | 2 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 256 | 2 | kv_list->list_methods->reverse(list); | |
| 257 | 2 | } | |
| 258 | |||
| 259 | 2595 | static void cx_kvl_list_iter_next(void *it) { | |
| 260 | 2595 | struct cx_iterator_s *iter = it; | |
| 261 |
2/2✓ Branch 0 (2→3) taken 59 times.
✓ Branch 1 (2→7) taken 2536 times.
|
2595 | if (iter->base.remove) { |
| 262 | // remove the assigned key from the map before calling the actual function | ||
| 263 | 59 | cx_kv_list *kv_list = iter->src_handle; | |
| 264 | 59 | cx_kv_list_update_destructors(kv_list); | |
| 265 | 59 | char *node = iter->elem_handle; | |
| 266 | 59 | CxHashKey *key = cx_kv_list_loc_key(kv_list, node + kv_list->list.loc_data); | |
| 267 |
2/2✓ Branch 0 (5→6) taken 1 times.
✓ Branch 1 (5→7) taken 58 times.
|
59 | if (key->hash != 0) { |
| 268 | 1 | kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); | |
| 269 | } | ||
| 270 | } | ||
| 271 | // note that we do not clear the remove flag, because the next_impl will do that | ||
| 272 | 2595 | iter->base.next_impl(it); | |
| 273 | 2595 | } | |
| 274 | |||
| 275 | 109 | static struct cx_iterator_s cx_kvl_iterator( | |
| 276 | const struct cx_list_s *list, | ||
| 277 | size_t index, | ||
| 278 | bool backward | ||
| 279 | ) { | ||
| 280 | 109 | const cx_kv_list *kv_list = (const cx_kv_list*)list; | |
| 281 | 109 | struct cx_iterator_s iter = kv_list->list_methods->iterator(list, index, backward); | |
| 282 | 109 | iter.base.next_impl = iter.base.next; | |
| 283 | 109 | iter.base.next = cx_kvl_list_iter_next; | |
| 284 | 109 | return iter; | |
| 285 | } | ||
| 286 | |||
| 287 | 165 | static void cx_kvl_map_deallocate(struct cx_map_s *map) { | |
| 288 | 165 | cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; | |
| 289 | 165 | cx_kv_list_update_destructors(kv_list); | |
| 290 | 165 | kv_list->map_methods->deallocate(map); | |
| 291 | 165 | kv_list->list_methods->deallocate(&kv_list->list.base); | |
| 292 | 165 | } | |
| 293 | |||
| 294 | 3 | static void cx_kvl_map_clear(struct cx_map_s *map) { | |
| 295 | 3 | cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; | |
| 296 | 3 | cx_kv_list_update_destructors(kv_list); | |
| 297 | 3 | kv_list->list_methods->clear(&kv_list->list.base); | |
| 298 | 3 | kv_list->map_methods->clear(map); | |
| 299 | 3 | } | |
| 300 | |||
| 301 | 326 | static void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { | |
| 302 | 326 | cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; | |
| 303 | 326 | void *node_data = kv_list->map_methods->get(map, key); | |
| 304 | − | if (node_data == NULL) return NULL; // LCOV_EXCL_LINE | |
| 305 | // return the node data | ||
| 306 |
2/2✓ Branch 0 (5→6) taken 261 times.
✓ Branch 1 (5→7) taken 41 times.
|
302 | return kv_list->list.base.collection.store_pointer ? *(void**)node_data : node_data; |
| 307 | } | ||
| 308 | |||
| 309 | 913 | static int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { | |
| 310 | 913 | cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; | |
| 311 | |||
| 312 | void *node_data; | ||
| 313 |
2/2✓ Branch 0 (3→4) taken 799 times.
✓ Branch 1 (3→5) taken 114 times.
|
913 | if (kv_list->map_methods->remove(map, key, &node_data)) { |
| 314 | 799 | return 1; | |
| 315 | } | ||
| 316 | // we cannot just call a list method (because we don't have the index) | ||
| 317 | // and tbh. we also don't want to (because it's not performant when we | ||
| 318 | // can have the node ptr directly instead) | ||
| 319 | // therefore, we re-implement the logic ourselves | ||
| 320 | |||
| 321 | // check if the outside caller want's us to return or to destroy the element | ||
| 322 |
2/2✓ Branch 0 (5→6) taken 112 times.
✓ Branch 1 (5→11) taken 2 times.
|
114 | if (targetbuf == NULL) { |
| 323 | // patch the destructors and invoke them through the wrapper | ||
| 324 | 112 | cx_kv_list_update_destructors(kv_list); | |
| 325 |
2/2✓ Branch 0 (7→8) taken 102 times.
✓ Branch 1 (7→9) taken 10 times.
|
112 | cx_invoke_advanced_destructor(&kv_list->list.base, node_data); |
| 326 | } else { | ||
| 327 | // copy the element to the target buffer | ||
| 328 | 2 | memcpy(targetbuf, node_data, kv_list->list.base.collection.elem_size); | |
| 329 | } | ||
| 330 | |||
| 331 | // calculate the address of the node | ||
| 332 | 114 | void *node_ptr = (char*)node_data - kv_list->list.loc_data; | |
| 333 | |||
| 334 | // unlink the node | ||
| 335 | 114 | cx_linked_list_remove( | |
| 336 | &kv_list->list.begin, | ||
| 337 | &kv_list->list.end, | ||
| 338 | 114 | kv_list->list.loc_prev, | |
| 339 | 114 | kv_list->list.loc_next, | |
| 340 | node_ptr | ||
| 341 | ); | ||
| 342 | |||
| 343 | // decrement the list's size | ||
| 344 | 114 | kv_list->list.base.collection.size--; | |
| 345 | |||
| 346 | // deallocate the node | ||
| 347 | 114 | cxFree(kv_list->list.base.collection.allocator, node_ptr); | |
| 348 | |||
| 349 | 114 | return 0; | |
| 350 | } | ||
| 351 | |||
| 352 | 897 | static CxMapEntry cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { | |
| 353 | 897 | cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; | |
| 354 | // if the hash has not yet been computed, do it now | ||
| 355 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 896 times.
|
897 | if (key.hash == 0) { |
| 356 | 1 | cx_hash_murmur(&key); | |
| 357 | } | ||
| 358 | |||
| 359 | // remove any existing element first | ||
| 360 | 897 | cx_kvl_map_remove(map, key, NULL); | |
| 361 | |||
| 362 | // now reserve new memory in the map | ||
| 363 | 897 | CxMapEntry map_entry = kv_list->map_methods->put(map, key, NULL); | |
| 364 | − | if (map_entry.key == NULL) return (CxMapEntry){NULL, NULL}; // LCOV_EXCL_LINE | |
| 365 | |||
| 366 | // insert the data into the list (which most likely destroys the sorted property) | ||
| 367 | 897 | kv_list->list.base.collection.sorted = false; | |
| 368 | 897 | void *node_data = kv_list->list_methods->insert_element( | |
| 369 | &kv_list->list.base, kv_list->list.base.collection.size, | ||
| 370 |
2/2✓ Branch 0 (9→10) taken 850 times.
✓ Branch 1 (9→11) taken 47 times.
|
897 | kv_list->list.base.collection.store_pointer ? &value : value); |
| 371 | − | if (node_data == NULL) { // LCOV_EXCL_START | |
| 372 | // non-destructively remove the key again | ||
| 373 | void *dummy; | ||
| 374 | − | kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &dummy); | |
| 375 | − | return (CxMapEntry){NULL, NULL}; | |
| 376 | } // LCOV_EXCL_STOP | ||
| 377 | |||
| 378 | // write the node pointer to the map entry | ||
| 379 | 897 | *(void**)map_entry.value = node_data; | |
| 380 | |||
| 381 | // copy the key to the node data | ||
| 382 | 897 | CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); | |
| 383 | 897 | *key_ptr = *map_entry.key; | |
| 384 | |||
| 385 | // we must return an entry that points to the node data! | ||
| 386 | 897 | return (CxMapEntry ){key_ptr, node_data}; | |
| 387 | } | ||
| 388 | |||
| 389 | 661 | static void *cx_kvl_iter_current_entry(const void *it) { | |
| 390 | 661 | const CxMapIterator *iter = it; | |
| 391 | 661 | return (void*)&iter->entry; | |
| 392 | } | ||
| 393 | |||
| 394 | 1 | static void *cx_kvl_iter_current_key(const void *it) { | |
| 395 | 1 | const CxMapEntry *entry = cx_kvl_iter_current_entry(it); | |
| 396 | 1 | return (void*)entry->key; | |
| 397 | } | ||
| 398 | |||
| 399 | 3 | static void *cx_kvl_iter_current_value(const void *it) { | |
| 400 | 3 | const CxMapEntry *entry = cx_kvl_iter_current_entry(it); | |
| 401 | 3 | return entry->value; | |
| 402 | } | ||
| 403 | |||
| 404 | 656 | static void cx_kvl_iter_next(void *it) { | |
| 405 | 656 | CxMapIterator *iter = it; | |
| 406 | 656 | cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)iter->map)->list; | |
| 407 | |||
| 408 | // find the next list entry that has a key assigned | ||
| 409 | 656 | CxHashKey *key = NULL; | |
| 410 | 656 | char *next = iter->elem; | |
| 411 | while (true) { | ||
| 412 | 657 | next = *(char**)(next + kv_list->list.loc_next); | |
| 413 |
2/2✓ Branch 0 (3→4) taken 132 times.
✓ Branch 1 (3→5) taken 525 times.
|
657 | if (next == NULL) break; |
| 414 | 525 | key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); | |
| 415 |
2/2✓ Branch 0 (6→7) taken 524 times.
✓ Branch 1 (6→8) taken 1 times.
|
525 | if (key->hash != 0) break; |
| 416 | } | ||
| 417 | |||
| 418 | // remove previous element if requested | ||
| 419 |
2/2✓ Branch 0 (9→10) taken 1 times.
✓ Branch 1 (9→20) taken 655 times.
|
656 | if (iter->base.remove) { |
| 420 | 1 | iter->base.remove = false; | |
| 421 | 1 | cx_kv_list_update_destructors(kv_list); | |
| 422 | 1 | char *elem = iter->elem; | |
| 423 | 1 | char *elem_data = elem + kv_list->list.loc_data; | |
| 424 | 1 | CxHashKey *elem_key = cx_kv_list_loc_key(kv_list, elem_data); | |
| 425 | // key is guaranteed to exist because iterator only iterates over elems with a key | ||
| 426 | 1 | kv_list->map_methods->remove(&kv_list->map->map_base.base, *elem_key, NULL); | |
| 427 |
1/2✓ Branch 0 (13→14) taken 1 times.
✗ Branch 1 (13→15) not taken.
|
1 | cx_invoke_advanced_destructor(&kv_list->list.base, elem_data); |
| 428 | 1 | cx_linked_list_remove( | |
| 429 | &kv_list->list.begin, | ||
| 430 | &kv_list->list.end, | ||
| 431 | 1 | kv_list->list.loc_prev, | |
| 432 | 1 | kv_list->list.loc_next, | |
| 433 | elem | ||
| 434 | ); | ||
| 435 | 1 | cxFree(kv_list->list.base.collection.allocator, elem); | |
| 436 | 1 | kv_list->list.base.collection.size--; | |
| 437 | 1 | iter->index--; | |
| 438 | 1 | iter->elem_count--; | |
| 439 | } | ||
| 440 | |||
| 441 | // advance to the next element, if any | ||
| 442 |
2/2✓ Branch 0 (20→21) taken 132 times.
✓ Branch 1 (20→22) taken 524 times.
|
656 | if (next == NULL) { |
| 443 | 132 | iter->index = kv_list->list.base.collection.size; | |
| 444 | 132 | iter->elem = NULL; | |
| 445 | 132 | iter->entry = (CxMapEntry){NULL, NULL}; | |
| 446 | 132 | return; | |
| 447 | } | ||
| 448 | 524 | iter->index++; | |
| 449 | 524 | iter->elem = next; | |
| 450 | 524 | iter->entry.key = key; | |
| 451 |
2/2✓ Branch 0 (22→23) taken 522 times.
✓ Branch 1 (22→24) taken 2 times.
|
524 | if (kv_list->list.base.collection.store_pointer) { |
| 452 | 522 | iter->entry.value = *(void**)(next + kv_list->list.loc_data); | |
| 453 | } else { | ||
| 454 | 2 | iter->entry.value = (void*)(next + kv_list->list.loc_data); | |
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | 680 | static bool cx_kvl_iter_valid(const void *it) { | |
| 459 | 680 | const CxMapIterator *iter = it; | |
| 460 | 680 | return iter->elem != NULL; | |
| 461 | } | ||
| 462 | |||
| 463 | 166 | static CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { | |
| 464 | 166 | CxMapIterator iter = {0}; | |
| 465 | |||
| 466 | 166 | iter.type = type; | |
| 467 | 166 | iter.map = (CxMap*)map; | |
| 468 | // although we iterate over the list, we only report that many elements that have a key in the map | ||
| 469 | 166 | iter.elem_count = map->collection.size; | |
| 470 | |||
| 471 |
3/4✓ Branch 0 (2→3) taken 164 times.
✓ Branch 1 (2→4) taken 1 times.
✓ Branch 2 (2→5) taken 1 times.
✗ Branch 3 (2→6) not taken.
|
166 | switch (type) { |
| 472 | 164 | case CX_MAP_ITERATOR_PAIRS: | |
| 473 | 164 | iter.elem_size = sizeof(CxMapEntry); | |
| 474 | 164 | iter.base.current = cx_kvl_iter_current_entry; | |
| 475 | 164 | break; | |
| 476 | 1 | case CX_MAP_ITERATOR_KEYS: | |
| 477 | 1 | iter.elem_size = sizeof(CxHashKey); | |
| 478 | 1 | iter.base.current = cx_kvl_iter_current_key; | |
| 479 | 1 | break; | |
| 480 | 1 | case CX_MAP_ITERATOR_VALUES: | |
| 481 | 1 | iter.elem_size = map->collection.elem_size; | |
| 482 | 1 | iter.base.current = cx_kvl_iter_current_value; | |
| 483 | 1 | break; | |
| 484 | 166 | default: | |
| 485 | assert(false); // LCOV_EXCL_LINE | ||
| 486 | } | ||
| 487 | |||
| 488 | 166 | iter.base.allow_remove = true; | |
| 489 | 166 | iter.base.next = cx_kvl_iter_next; | |
| 490 | 166 | iter.base.valid = cx_kvl_iter_valid; | |
| 491 | |||
| 492 | // find the first list entry that has a key assigned | ||
| 493 | 166 | cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; | |
| 494 | 166 | CxHashKey *key = NULL; | |
| 495 | 166 | char *next = kv_list->list.begin; | |
| 496 |
2/2✓ Branch 0 (11→7) taken 141 times.
✓ Branch 1 (11→12) taken 29 times.
|
170 | while (next != NULL) { |
| 497 | 141 | key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); | |
| 498 |
2/2✓ Branch 0 (8→9) taken 137 times.
✓ Branch 1 (8→10) taken 4 times.
|
141 | if (key->hash != 0) break; |
| 499 | 4 | next = *(char**)(next + kv_list->list.loc_next); | |
| 500 | } | ||
| 501 |
2/2✓ Branch 0 (12→13) taken 29 times.
✓ Branch 1 (12→14) taken 137 times.
|
166 | if (next == NULL) { |
| 502 | 29 | iter.elem = NULL; | |
| 503 | 29 | iter.entry = (CxMapEntry){NULL, NULL}; | |
| 504 | } else { | ||
| 505 | 137 | iter.elem = next; | |
| 506 | 137 | iter.entry.key = key; | |
| 507 |
2/2✓ Branch 0 (14→15) taken 135 times.
✓ Branch 1 (14→16) taken 2 times.
|
137 | if (kv_list->list.base.collection.store_pointer) { |
| 508 | 135 | iter.entry.value = *(void**)(next + kv_list->list.loc_data); | |
| 509 | } else { | ||
| 510 | 2 | iter.entry.value = (void*)(next + kv_list->list.loc_data); | |
| 511 | } | ||
| 512 | } | ||
| 513 | |||
| 514 | 166 | return iter; | |
| 515 | } | ||
| 516 | |||
| 517 | 4 | static int cx_kvl_change_capacity(struct cx_list_s *list, | |
| 518 | CX_UNUSED size_t cap) { | ||
| 519 | // since our backing list is a linked list, we don't need to do much here, | ||
| 520 | // but rehashing the map is quite useful | ||
| 521 | 4 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 522 | 4 | cxMapRehash(&kv_list->map->map_base.base); | |
| 523 | 4 | return 0; | |
| 524 | } | ||
| 525 | |||
| 526 | static cx_list_class cx_kv_list_class = { | ||
| 527 | cx_kvl_deallocate, | ||
| 528 | cx_kvl_insert_element, | ||
| 529 | cx_kvl_insert_array, | ||
| 530 | cx_kvl_insert_sorted, | ||
| 531 | cx_kvl_insert_unique, | ||
| 532 | cx_kvl_insert_iter, | ||
| 533 | cx_kvl_remove, | ||
| 534 | cx_kvl_clear, | ||
| 535 | cx_kvl_swap, | ||
| 536 | cx_kvl_at, | ||
| 537 | cx_kvl_find_remove, | ||
| 538 | cx_kvl_sort, | ||
| 539 | NULL, | ||
| 540 | cx_kvl_reverse, | ||
| 541 | cx_kvl_change_capacity, | ||
| 542 | cx_kvl_iterator, | ||
| 543 | }; | ||
| 544 | |||
| 545 | static cx_map_class cx_kv_map_class = { | ||
| 546 | cx_kvl_map_deallocate, | ||
| 547 | cx_kvl_map_clear, | ||
| 548 | cx_kvl_map_put, | ||
| 549 | cx_kvl_map_get, | ||
| 550 | cx_kvl_map_remove, | ||
| 551 | cx_kvl_map_iterator, | ||
| 552 | }; | ||
| 553 | |||
| 554 | 278 | CxList *cxKvListCreate( | |
| 555 | const CxAllocator *allocator, | ||
| 556 | size_t elem_size | ||
| 557 | ) { | ||
| 558 |
2/2✓ Branch 0 (2→3) taken 37 times.
✓ Branch 1 (2→4) taken 241 times.
|
278 | if (allocator == NULL) { |
| 559 | 37 | allocator = cxDefaultAllocator; | |
| 560 | } | ||
| 561 | |||
| 562 | // create a normal linked list and a normal hash map, first | ||
| 563 | 278 | CxList *list = cxLinkedListCreate(allocator, elem_size); | |
| 564 | − | if (list == NULL) return NULL; // LCOV_EXCL_LINE | |
| 565 | 278 | cx_linked_list *ll = (cx_linked_list*)list; | |
| 566 | 278 | cx_linked_list_extra_data(ll, sizeof(CxHashKey)); | |
| 567 | 278 | CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0); | |
| 568 | − | if (map == NULL) { // LCOV_EXCL_START | |
| 569 | − | cxListFree(list); | |
| 570 | − | return NULL; | |
| 571 | } // LCOV_EXCL_STOP | ||
| 572 | |||
| 573 | // patch the kv-list class with the compare function of the linked list | ||
| 574 | // this allows cxListCompare() to optimize comparisons between linked lists and kv-list | ||
| 575 | 278 | cx_kv_list_class.compare = list->cl->compare; | |
| 576 | |||
| 577 | // reallocate the map to add memory for the list back-reference | ||
| 578 | 278 | struct cx_kv_list_map_s *kv_map = cxRealloc(allocator, map, sizeof(struct cx_kv_list_map_s)); | |
| 579 | |||
| 580 | // reallocate the list to add memory for storing the metadata | ||
| 581 | 278 | cx_kv_list *kv_list = cxRealloc(allocator, list, sizeof(struct cx_kv_list_s)); | |
| 582 | |||
| 583 | // if any of the reallocations failed, we bail out | ||
| 584 |
2/4✓ Branch 0 (14→15) taken 278 times.
✗ Branch 1 (14→17) not taken.
✓ Branch 2 (15→16) taken 278 times.
✗ Branch 3 (15→17) not taken.
|
278 | if (kv_map != NULL && kv_list != NULL) { |
| 585 | 278 | map = (CxMap*) kv_map; | |
| 586 | 278 | list = (CxList*) kv_list; | |
| 587 | } else { // LCOV_EXCL_START | ||
| 588 | − | cxListFree(list); | |
| 589 | − | cxMapFree(map); | |
| 590 | − | return NULL; | |
| 591 | } // LCOV_EXCL_STOP | ||
| 592 | |||
| 593 | // zero the custom destructor information | ||
| 594 | 278 | memset((char*)kv_list + offsetof(cx_kv_list, list_destr), 0, sizeof(void*)*6); | |
| 595 | |||
| 596 | // combine the list and the map aspect | ||
| 597 | 278 | kv_list->map = kv_map; | |
| 598 | 278 | kv_map->list = kv_list; | |
| 599 | |||
| 600 | // remember the base methods and override them | ||
| 601 | 278 | kv_list->map_methods = map->cl; | |
| 602 | 278 | map->cl = &cx_kv_map_class; | |
| 603 | 278 | kv_list->list_methods = list->cl; | |
| 604 | 278 | list->cl = &cx_kv_list_class; | |
| 605 | |||
| 606 | 278 | return list; | |
| 607 | } | ||
| 608 | |||
| 609 | 165 | CxMap *cxKvListCreateAsMap( | |
| 610 | const CxAllocator *allocator, | ||
| 611 | size_t elem_size | ||
| 612 | ) { | ||
| 613 | 165 | CxList *list = cxKvListCreate(allocator, elem_size); | |
| 614 |
1/2✓ Branch 0 (3→4) taken 165 times.
✗ Branch 1 (3→5) not taken.
|
165 | return list == NULL ? NULL : cxKvListAsMap(list); |
| 615 | } | ||
| 616 | |||
| 617 | 11 | CxList *cxKvListAsList(CxMap *map) { | |
| 618 | 11 | return &((struct cx_kv_list_map_s*)map)->list->list.base; | |
| 619 | } | ||
| 620 | |||
| 621 | 189 | CxMap *cxKvListAsMap(CxList *list) { | |
| 622 | 189 | return &((cx_kv_list*)list)->map->map_base.base; | |
| 623 | } | ||
| 624 | |||
| 625 | 19 | int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key) { | |
| 626 | 19 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 627 | 19 | void *node_data = kv_list->list_methods->at(list, index); | |
| 628 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→5) taken 18 times.
|
19 | if (node_data == NULL) { |
| 629 | 1 | return 1; | |
| 630 | } | ||
| 631 | // if the hash has not yet been computed, do it now | ||
| 632 |
2/2✓ Branch 0 (5→6) taken 1 times.
✓ Branch 1 (5→7) taken 17 times.
|
18 | if (key.hash == 0) { |
| 633 | 1 | cx_hash_murmur(&key); | |
| 634 | } | ||
| 635 | |||
| 636 | // check if the key is already assigned | ||
| 637 | 18 | void *existing = kv_list->map_methods->get(&kv_list->map->map_base.base, key); | |
| 638 |
2/2✓ Branch 0 (8→9) taken 1 times.
✓ Branch 1 (8→10) taken 17 times.
|
18 | if (existing == node_data) { |
| 639 | 1 | return 0; // nothing to do | |
| 640 | } | ||
| 641 |
2/2✓ Branch 0 (10→11) taken 1 times.
✓ Branch 1 (10→12) taken 16 times.
|
17 | if (existing != NULL) { |
| 642 | // the key is already assigned to another node, we disallow re-assignment | ||
| 643 | 1 | return 1; | |
| 644 | } | ||
| 645 | |||
| 646 | // add the key to the map | ||
| 647 | 16 | const CxMapEntry entry = kv_list->map_methods->put( | |
| 648 | 16 | &kv_list->map->map_base.base, key, node_data); | |
| 649 | − | if (entry.key == NULL) return 1; // LCOV_EXCL_LINE | |
| 650 | |||
| 651 | // write the key to the list's node | ||
| 652 | 16 | CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); | |
| 653 | 16 | *loc_key = *entry.key; | |
| 654 | |||
| 655 | 16 | return 0; | |
| 656 | } | ||
| 657 | |||
| 658 | 5 | int cxKvListRemoveKey(CxList *list, size_t index) { | |
| 659 | 5 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 660 | 5 | void *node_data = kv_list->list_methods->at(list, index); | |
| 661 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→5) taken 4 times.
|
5 | if (node_data == NULL) { |
| 662 | 1 | return 1; | |
| 663 | } | ||
| 664 | 4 | CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); | |
| 665 |
2/2✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→8) taken 3 times.
|
4 | if (loc_key->hash == 0) { |
| 666 | 1 | return 0; | |
| 667 | } | ||
| 668 | 3 | kv_list->map_methods->remove(&kv_list->map->map_base.base, *loc_key, NULL); | |
| 669 | // also zero the memory in the list node, | ||
| 670 | // but don't free the key data (that was done by the map remove) | ||
| 671 | 3 | memset(loc_key, 0, sizeof(CxHashKey)); | |
| 672 | 3 | return 0; | |
| 673 | } | ||
| 674 | |||
| 675 | 4 | const CxHashKey *cxKvListGetKey(CxList *list, size_t index) { | |
| 676 | 4 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 677 | 4 | void *node_data = kv_list->list_methods->at(list, index); | |
| 678 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→5) taken 3 times.
|
4 | if (node_data == NULL) { |
| 679 | 1 | return NULL; | |
| 680 | } | ||
| 681 | 3 | CxHashKey *key = cx_kv_list_loc_key(kv_list, node_data); | |
| 682 |
2/2✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→8) taken 2 times.
|
3 | if (key->hash == 0) { |
| 683 | 1 | return NULL; | |
| 684 | } | ||
| 685 | 2 | return key; | |
| 686 | } | ||
| 687 | |||
| 688 | 6 | int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value) { | |
| 689 | // assume we are losing the sorted property | ||
| 690 | 6 | list->collection.sorted = false; | |
| 691 | |||
| 692 | 6 | cx_kv_list *kv_list = (cx_kv_list*)list; | |
| 693 | |||
| 694 | // reserve memory in the map | ||
| 695 | 6 | CxMapEntry map_entry = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL); | |
| 696 | − | if (map_entry.key == NULL) return 1; // LCOV_EXCL_LINE | |
| 697 | |||
| 698 | // insert the node | ||
| 699 | 6 | void *node_data = kv_list->list_methods->insert_element(&kv_list->list.base, index, | |
| 700 |
2/2✓ Branch 0 (5→6) taken 3 times.
✓ Branch 1 (5→7) taken 3 times.
|
6 | kv_list->list.base.collection.store_pointer ? &value : value); |
| 701 | − | if (node_data == NULL) { // LCOV_EXCL_START | |
| 702 | // non-destructively remove the key again | ||
| 703 | void *dummy; | ||
| 704 | − | kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &dummy); | |
| 705 | − | return 1; | |
| 706 | } // LCOV_EXCL_STOP | ||
| 707 | 6 | *(void**)map_entry.value = node_data; | |
| 708 | |||
| 709 | // write the key to the node | ||
| 710 | 6 | CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); | |
| 711 | 6 | *loc_key = *map_entry.key; | |
| 712 | |||
| 713 | 6 | return 0; | |
| 714 | } | ||
| 715 |