| |
1 /* |
| |
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| |
3 * |
| |
4 * Copyright 2021 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/array_list.h" |
| |
30 #include "cx/compare.h" |
| |
31 #include <assert.h> |
| |
32 #include <string.h> |
| |
33 |
| |
34 // Default array reallocator |
| |
35 |
| |
36 static void *cx_array_default_realloc( |
| |
37 void *array, |
| |
38 size_t capacity, |
| |
39 size_t elem_size, |
| |
40 __attribute__((__unused__)) struct cx_array_reallocator_s *alloc |
| |
41 ) { |
| |
42 return realloc(array, capacity * elem_size); |
| |
43 } |
| |
44 |
| |
45 struct cx_array_reallocator_s cx_array_default_reallocator_impl = { |
| |
46 cx_array_default_realloc, NULL, NULL, 0, 0 |
| |
47 }; |
| |
48 |
| |
49 struct cx_array_reallocator_s *cx_array_default_reallocator = &cx_array_default_reallocator_impl; |
| |
50 |
| |
51 // LOW LEVEL ARRAY LIST FUNCTIONS |
| |
52 |
| |
53 enum cx_array_result cx_array_copy( |
| |
54 void **target, |
| |
55 size_t *size, |
| |
56 size_t *capacity, |
| |
57 size_t index, |
| |
58 void const *src, |
| |
59 size_t elem_size, |
| |
60 size_t elem_count, |
| |
61 struct cx_array_reallocator_s *reallocator |
| |
62 ) { |
| |
63 // assert pointers |
| |
64 assert(target != NULL); |
| |
65 assert(size != NULL); |
| |
66 assert(src != NULL); |
| |
67 |
| |
68 // determine capacity |
| |
69 size_t cap = capacity == NULL ? *size : *capacity; |
| |
70 |
| |
71 // check if resize is required |
| |
72 size_t minsize = index + elem_count; |
| |
73 size_t newsize = *size < minsize ? minsize : *size; |
| |
74 bool needrealloc = newsize > cap; |
| |
75 |
| |
76 // reallocate if possible |
| |
77 if (needrealloc) { |
| |
78 // a reallocator and a capacity variable must be available |
| |
79 if (reallocator == NULL || capacity == NULL) { |
| |
80 return CX_ARRAY_REALLOC_NOT_SUPPORTED; |
| |
81 } |
| |
82 |
| |
83 // check, if we need to repair the src pointer |
| |
84 uintptr_t targetaddr = (uintptr_t) *target; |
| |
85 uintptr_t srcaddr = (uintptr_t) src; |
| |
86 bool repairsrc = targetaddr <= srcaddr |
| |
87 && srcaddr < targetaddr + cap * elem_size; |
| |
88 |
| |
89 // calculate new capacity (next number divisible by 16) |
| |
90 cap = newsize - (newsize % 16) + 16; |
| |
91 assert(cap > newsize); |
| |
92 |
| |
93 // perform reallocation |
| |
94 void *newmem = reallocator->realloc( |
| |
95 *target, cap, elem_size, reallocator |
| |
96 ); |
| |
97 if (newmem == NULL) { |
| |
98 return CX_ARRAY_REALLOC_FAILED; |
| |
99 } |
| |
100 |
| |
101 // repair src pointer, if necessary |
| |
102 if (repairsrc) { |
| |
103 src = ((char *) newmem) + (srcaddr - targetaddr); |
| |
104 } |
| |
105 |
| |
106 // store new pointer and capacity |
| |
107 *target = newmem; |
| |
108 *capacity = cap; |
| |
109 } |
| |
110 |
| |
111 // determine target pointer |
| |
112 char *start = *target; |
| |
113 start += index * elem_size; |
| |
114 |
| |
115 // copy elements and set new size |
| |
116 memmove(start, src, elem_count * elem_size); |
| |
117 *size = newsize; |
| |
118 |
| |
119 // return successfully |
| |
120 return CX_ARRAY_SUCCESS; |
| |
121 } |
| |
122 |
| |
123 #ifndef CX_ARRAY_SWAP_SBO_SIZE |
| |
124 #define CX_ARRAY_SWAP_SBO_SIZE 128 |
| |
125 #endif |
| |
126 unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE; |
| |
127 |
| |
128 void cx_array_swap( |
| |
129 void *arr, |
| |
130 size_t elem_size, |
| |
131 size_t idx1, |
| |
132 size_t idx2 |
| |
133 ) { |
| |
134 assert(arr != NULL); |
| |
135 |
| |
136 // short circuit |
| |
137 if (idx1 == idx2) return; |
| |
138 |
| |
139 char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE]; |
| |
140 void *tmp; |
| |
141 |
| |
142 // decide if we can use the local buffer |
| |
143 if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) { |
| |
144 tmp = malloc(elem_size); |
| |
145 // we don't want to enforce error handling |
| |
146 if (tmp == NULL) abort(); |
| |
147 } else { |
| |
148 tmp = sbo_mem; |
| |
149 } |
| |
150 |
| |
151 // calculate memory locations |
| |
152 char *left = arr, *right = arr; |
| |
153 left += idx1 * elem_size; |
| |
154 right += idx2 * elem_size; |
| |
155 |
| |
156 // three-way swap |
| |
157 memcpy(tmp, left, elem_size); |
| |
158 memcpy(left, right, elem_size); |
| |
159 memcpy(right, tmp, elem_size); |
| |
160 |
| |
161 // free dynamic memory, if it was needed |
| |
162 if (tmp != sbo_mem) { |
| |
163 free(tmp); |
| |
164 } |
| |
165 } |
| |
166 |
| |
167 // HIGH LEVEL ARRAY LIST FUNCTIONS |
| |
168 |
| |
169 typedef struct { |
| |
170 struct cx_list_s base; |
| |
171 void *data; |
| |
172 size_t capacity; |
| |
173 struct cx_array_reallocator_s reallocator; |
| |
174 } cx_array_list; |
| |
175 |
| |
176 static void *cx_arl_realloc( |
| |
177 void *array, |
| |
178 size_t capacity, |
| |
179 size_t elem_size, |
| |
180 struct cx_array_reallocator_s *alloc |
| |
181 ) { |
| |
182 // retrieve the pointer to the list allocator |
| |
183 CxAllocator const *al = alloc->ptr1; |
| |
184 |
| |
185 // use the list allocator to reallocate the memory |
| |
186 return cxRealloc(al, array, capacity * elem_size); |
| |
187 } |
| |
188 |
| |
189 static void cx_arl_destructor(struct cx_list_s *list) { |
| |
190 cx_array_list *arl = (cx_array_list *) list; |
| |
191 |
| |
192 char *ptr = arl->data; |
| |
193 |
| |
194 if (list->collection.simple_destructor) { |
| |
195 for (size_t i = 0; i < list->collection.size; i++) { |
| |
196 cx_invoke_simple_destructor(list, ptr); |
| |
197 ptr += list->collection.elem_size; |
| |
198 } |
| |
199 } |
| |
200 if (list->collection.advanced_destructor) { |
| |
201 for (size_t i = 0; i < list->collection.size; i++) { |
| |
202 cx_invoke_advanced_destructor(list, ptr); |
| |
203 ptr += list->collection.elem_size; |
| |
204 } |
| |
205 } |
| |
206 |
| |
207 cxFree(list->collection.allocator, arl->data); |
| |
208 cxFree(list->collection.allocator, list); |
| |
209 } |
| |
210 |
| |
211 static size_t cx_arl_insert_array( |
| |
212 struct cx_list_s *list, |
| |
213 size_t index, |
| |
214 void const *array, |
| |
215 size_t n |
| |
216 ) { |
| |
217 // out of bounds and special case check |
| |
218 if (index > list->collection.size || n == 0) return 0; |
| |
219 |
| |
220 // get a correctly typed pointer to the list |
| |
221 cx_array_list *arl = (cx_array_list *) list; |
| |
222 |
| |
223 // do we need to move some elements? |
| |
224 if (index < list->collection.size) { |
| |
225 char const *first_to_move = (char const *) arl->data; |
| |
226 first_to_move += index * list->collection.elem_size; |
| |
227 size_t elems_to_move = list->collection.size - index; |
| |
228 size_t start_of_moved = index + n; |
| |
229 |
| |
230 if (CX_ARRAY_SUCCESS != cx_array_copy( |
| |
231 &arl->data, |
| |
232 &list->collection.size, |
| |
233 &arl->capacity, |
| |
234 start_of_moved, |
| |
235 first_to_move, |
| |
236 list->collection.elem_size, |
| |
237 elems_to_move, |
| |
238 &arl->reallocator |
| |
239 )) { |
| |
240 // if moving existing elems is unsuccessful, abort |
| |
241 return 0; |
| |
242 } |
| |
243 } |
| |
244 |
| |
245 // note that if we had to move the elements, the following operation |
| |
246 // is guaranteed to succeed, because we have the memory already allocated |
| |
247 // therefore, it is impossible to leave this function with an invalid array |
| |
248 |
| |
249 // place the new elements |
| |
250 if (CX_ARRAY_SUCCESS == cx_array_copy( |
| |
251 &arl->data, |
| |
252 &list->collection.size, |
| |
253 &arl->capacity, |
| |
254 index, |
| |
255 array, |
| |
256 list->collection.elem_size, |
| |
257 n, |
| |
258 &arl->reallocator |
| |
259 )) { |
| |
260 return n; |
| |
261 } else { |
| |
262 // array list implementation is "all or nothing" |
| |
263 return 0; |
| |
264 } |
| |
265 } |
| |
266 |
| |
267 static int cx_arl_insert_element( |
| |
268 struct cx_list_s *list, |
| |
269 size_t index, |
| |
270 void const *element |
| |
271 ) { |
| |
272 return 1 != cx_arl_insert_array(list, index, element, 1); |
| |
273 } |
| |
274 |
| |
275 static int cx_arl_insert_iter( |
| |
276 struct cx_iterator_s *iter, |
| |
277 void const *elem, |
| |
278 int prepend |
| |
279 ) { |
| |
280 struct cx_list_s *list = iter->src_handle.m; |
| |
281 if (iter->index < list->collection.size) { |
| |
282 int result = cx_arl_insert_element( |
| |
283 list, |
| |
284 iter->index + 1 - prepend, |
| |
285 elem |
| |
286 ); |
| |
287 if (result == 0 && prepend != 0) { |
| |
288 iter->index++; |
| |
289 iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size; |
| |
290 } |
| |
291 return result; |
| |
292 } else { |
| |
293 int result = cx_arl_insert_element(list, list->collection.size, elem); |
| |
294 iter->index = list->collection.size; |
| |
295 return result; |
| |
296 } |
| |
297 } |
| |
298 |
| |
299 static int cx_arl_remove( |
| |
300 struct cx_list_s *list, |
| |
301 size_t index |
| |
302 ) { |
| |
303 cx_array_list *arl = (cx_array_list *) list; |
| |
304 |
| |
305 // out-of-bounds check |
| |
306 if (index >= list->collection.size) { |
| |
307 return 1; |
| |
308 } |
| |
309 |
| |
310 // content destruction |
| |
311 cx_invoke_destructor(list, ((char *) arl->data) + index * list->collection.elem_size); |
| |
312 |
| |
313 // short-circuit removal of last element |
| |
314 if (index == list->collection.size - 1) { |
| |
315 list->collection.size--; |
| |
316 return 0; |
| |
317 } |
| |
318 |
| |
319 // just move the elements starting at index to the left |
| |
320 int result = cx_array_copy( |
| |
321 &arl->data, |
| |
322 &list->collection.size, |
| |
323 &arl->capacity, |
| |
324 index, |
| |
325 ((char *) arl->data) + (index + 1) * list->collection.elem_size, |
| |
326 list->collection.elem_size, |
| |
327 list->collection.size - index - 1, |
| |
328 &arl->reallocator |
| |
329 ); |
| |
330 |
| |
331 // cx_array_copy cannot fail, array cannot grow |
| |
332 assert(result == 0); |
| |
333 |
| |
334 // decrease the size |
| |
335 list->collection.size--; |
| |
336 |
| |
337 return 0; |
| |
338 } |
| |
339 |
| |
340 static void cx_arl_clear(struct cx_list_s *list) { |
| |
341 if (list->collection.size == 0) return; |
| |
342 |
| |
343 cx_array_list *arl = (cx_array_list *) list; |
| |
344 char *ptr = arl->data; |
| |
345 |
| |
346 if (list->collection.simple_destructor) { |
| |
347 for (size_t i = 0; i < list->collection.size; i++) { |
| |
348 cx_invoke_simple_destructor(list, ptr); |
| |
349 ptr += list->collection.elem_size; |
| |
350 } |
| |
351 } |
| |
352 if (list->collection.advanced_destructor) { |
| |
353 for (size_t i = 0; i < list->collection.size; i++) { |
| |
354 cx_invoke_advanced_destructor(list, ptr); |
| |
355 ptr += list->collection.elem_size; |
| |
356 } |
| |
357 } |
| |
358 |
| |
359 memset(arl->data, 0, list->collection.size * list->collection.elem_size); |
| |
360 list->collection.size = 0; |
| |
361 } |
| |
362 |
| |
363 static int cx_arl_swap( |
| |
364 struct cx_list_s *list, |
| |
365 size_t i, |
| |
366 size_t j |
| |
367 ) { |
| |
368 if (i >= list->collection.size || j >= list->collection.size) return 1; |
| |
369 cx_array_list *arl = (cx_array_list *) list; |
| |
370 cx_array_swap(arl->data, list->collection.elem_size, i, j); |
| |
371 return 0; |
| |
372 } |
| |
373 |
| |
374 static void *cx_arl_at( |
| |
375 struct cx_list_s const *list, |
| |
376 size_t index |
| |
377 ) { |
| |
378 if (index < list->collection.size) { |
| |
379 cx_array_list const *arl = (cx_array_list const *) list; |
| |
380 char *space = arl->data; |
| |
381 return space + index * list->collection.elem_size; |
| |
382 } else { |
| |
383 return NULL; |
| |
384 } |
| |
385 } |
| |
386 |
| |
387 static ssize_t cx_arl_find_remove( |
| |
388 struct cx_list_s *list, |
| |
389 void const *elem, |
| |
390 bool remove |
| |
391 ) { |
| |
392 assert(list->collection.cmpfunc != NULL); |
| |
393 assert(list->collection.size < SIZE_MAX / 2); |
| |
394 char *cur = ((cx_array_list const *) list)->data; |
| |
395 |
| |
396 for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) { |
| |
397 if (0 == list->collection.cmpfunc(elem, cur)) { |
| |
398 if (remove) { |
| |
399 if (0 == cx_arl_remove(list, i)) { |
| |
400 return i; |
| |
401 } else { |
| |
402 return -1; |
| |
403 } |
| |
404 } else { |
| |
405 return i; |
| |
406 } |
| |
407 } |
| |
408 cur += list->collection.elem_size; |
| |
409 } |
| |
410 |
| |
411 return -1; |
| |
412 } |
| |
413 |
| |
414 static void cx_arl_sort(struct cx_list_s *list) { |
| |
415 assert(list->collection.cmpfunc != NULL); |
| |
416 qsort(((cx_array_list *) list)->data, |
| |
417 list->collection.size, |
| |
418 list->collection.elem_size, |
| |
419 list->collection.cmpfunc |
| |
420 ); |
| |
421 } |
| |
422 |
| |
423 static int cx_arl_compare( |
| |
424 struct cx_list_s const *list, |
| |
425 struct cx_list_s const *other |
| |
426 ) { |
| |
427 assert(list->collection.cmpfunc != NULL); |
| |
428 if (list->collection.size == other->collection.size) { |
| |
429 char const *left = ((cx_array_list const *) list)->data; |
| |
430 char const *right = ((cx_array_list const *) other)->data; |
| |
431 for (size_t i = 0; i < list->collection.size; i++) { |
| |
432 int d = list->collection.cmpfunc(left, right); |
| |
433 if (d != 0) { |
| |
434 return d; |
| |
435 } |
| |
436 left += list->collection.elem_size; |
| |
437 right += other->collection.elem_size; |
| |
438 } |
| |
439 return 0; |
| |
440 } else { |
| |
441 return list->collection.size < other->collection.size ? -1 : 1; |
| |
442 } |
| |
443 } |
| |
444 |
| |
445 static void cx_arl_reverse(struct cx_list_s *list) { |
| |
446 if (list->collection.size < 2) return; |
| |
447 void *data = ((cx_array_list const *) list)->data; |
| |
448 size_t half = list->collection.size / 2; |
| |
449 for (size_t i = 0; i < half; i++) { |
| |
450 cx_array_swap(data, list->collection.elem_size, i, list->collection.size - 1 - i); |
| |
451 } |
| |
452 } |
| |
453 |
| |
454 static bool cx_arl_iter_valid(void const *it) { |
| |
455 struct cx_iterator_s const *iter = it; |
| |
456 struct cx_list_s const *list = iter->src_handle.c; |
| |
457 return iter->index < list->collection.size; |
| |
458 } |
| |
459 |
| |
460 static void *cx_arl_iter_current(void const *it) { |
| |
461 struct cx_iterator_s const *iter = it; |
| |
462 return iter->elem_handle; |
| |
463 } |
| |
464 |
| |
465 static void cx_arl_iter_next(void *it) { |
| |
466 struct cx_iterator_s *iter = it; |
| |
467 if (iter->base.remove) { |
| |
468 iter->base.remove = false; |
| |
469 cx_arl_remove(iter->src_handle.m, iter->index); |
| |
470 } else { |
| |
471 iter->index++; |
| |
472 iter->elem_handle = |
| |
473 ((char *) iter->elem_handle) |
| |
474 + ((struct cx_list_s const *) iter->src_handle.c)->collection.elem_size; |
| |
475 } |
| |
476 } |
| |
477 |
| |
478 static void cx_arl_iter_prev(void *it) { |
| |
479 struct cx_iterator_s *iter = it; |
| |
480 cx_array_list const* list = iter->src_handle.c; |
| |
481 if (iter->base.remove) { |
| |
482 iter->base.remove = false; |
| |
483 cx_arl_remove(iter->src_handle.m, iter->index); |
| |
484 } |
| |
485 iter->index--; |
| |
486 if (iter->index < list->base.collection.size) { |
| |
487 iter->elem_handle = ((char *) list->data) |
| |
488 + iter->index * list->base.collection.elem_size; |
| |
489 } |
| |
490 } |
| |
491 |
| |
492 |
| |
493 static struct cx_iterator_s cx_arl_iterator( |
| |
494 struct cx_list_s const *list, |
| |
495 size_t index, |
| |
496 bool backwards |
| |
497 ) { |
| |
498 struct cx_iterator_s iter; |
| |
499 |
| |
500 iter.index = index; |
| |
501 iter.src_handle.c = list; |
| |
502 iter.elem_handle = cx_arl_at(list, index); |
| |
503 iter.elem_size = list->collection.elem_size; |
| |
504 iter.elem_count = list->collection.size; |
| |
505 iter.base.valid = cx_arl_iter_valid; |
| |
506 iter.base.current = cx_arl_iter_current; |
| |
507 iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next; |
| |
508 iter.base.remove = false; |
| |
509 iter.base.mutating = false; |
| |
510 |
| |
511 return iter; |
| |
512 } |
| |
513 |
| |
514 static cx_list_class cx_array_list_class = { |
| |
515 cx_arl_destructor, |
| |
516 cx_arl_insert_element, |
| |
517 cx_arl_insert_array, |
| |
518 cx_arl_insert_iter, |
| |
519 cx_arl_remove, |
| |
520 cx_arl_clear, |
| |
521 cx_arl_swap, |
| |
522 cx_arl_at, |
| |
523 cx_arl_find_remove, |
| |
524 cx_arl_sort, |
| |
525 cx_arl_compare, |
| |
526 cx_arl_reverse, |
| |
527 cx_arl_iterator, |
| |
528 }; |
| |
529 |
| |
530 CxList *cxArrayListCreate( |
| |
531 CxAllocator const *allocator, |
| |
532 cx_compare_func comparator, |
| |
533 size_t elem_size, |
| |
534 size_t initial_capacity |
| |
535 ) { |
| |
536 if (allocator == NULL) { |
| |
537 allocator = cxDefaultAllocator; |
| |
538 } |
| |
539 |
| |
540 cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list)); |
| |
541 if (list == NULL) return NULL; |
| |
542 |
| |
543 list->base.cl = &cx_array_list_class; |
| |
544 list->base.collection.allocator = allocator; |
| |
545 list->capacity = initial_capacity; |
| |
546 |
| |
547 if (elem_size > 0) { |
| |
548 list->base.collection.elem_size = elem_size; |
| |
549 list->base.collection.cmpfunc = comparator; |
| |
550 } else { |
| |
551 elem_size = sizeof(void *); |
| |
552 list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator; |
| |
553 cxListStorePointers((CxList *) list); |
| |
554 } |
| |
555 |
| |
556 // allocate the array after the real elem_size is known |
| |
557 list->data = cxCalloc(allocator, initial_capacity, elem_size); |
| |
558 if (list->data == NULL) { |
| |
559 cxFree(allocator, list); |
| |
560 return NULL; |
| |
561 } |
| |
562 |
| |
563 // configure the reallocator |
| |
564 list->reallocator.realloc = cx_arl_realloc; |
| |
565 list->reallocator.ptr1 = (void *) allocator; |
| |
566 |
| |
567 return (CxList *) list; |
| |
568 } |