diff --git a/include/QF/darray.h b/include/QF/darray.h new file mode 100644 index 000000000..88792715f --- /dev/null +++ b/include/QF/darray.h @@ -0,0 +1,301 @@ +/* + darray.h + + Dynamic arrays + + Copyright (C) 2020 Bill Currie + + Author: Bill Currie + Date: 2020/02/17 + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef __pr_type_h +#define __pr_type_h + +/** \defgroup darray Dynamic Arrays + \ingroup utils + + Dynamic array container object +*/ +///@{ + +/** The structure defs for a dynamic array with elements of the given type. + + This is just the defs of a struct delcaration: it is useless on its own. + The intended usage is something like: + + typedef struct dynamic_array_s DARRAY_TYPE(int) dynamic_array_t; + + This allows full flexibility in just how the actual type is used. + + The \a size field is the number of elements currently in the array, and + the \a maxSize field is the number of elements the array can hold without + being resized. + + The \a grow field specifies the number of elements by which \a maxSize is + to grow when the array needs to be resized. Setting this to 0 prevents + resizing and any attempt to do so is a fatal error. + + \param ele_type The type to use for the element array, which is accessed + by the \a a field. + \hideinitializer +*/ +#define DARRAY_TYPE(ele_type) \ + { \ + size_t size; \ + size_t maxSize; \ + size_t grow; \ + ele_type *a; \ + } + +/** Clear the array. + + If the array can grow, its backing will be freed and maxSize and a reset, + otherwise maxSize and a are left untouched. + + \param array *Address* of the array to be modified (ie, pointer to the + array struct instance, not the instance itself: use & for + static instances of the array struct). + \hideinitializer +*/ +#define DARRAY_CLEAR(array) \ + do { \ + __auto_type ar = (array); \ + free (ar->a); \ + ar->size = 0; \ + if (ar->grow) { \ + ar->maxSize = 0; \ + ar->a = 0; \ + } \ + } while (0) + +/** Set the size of the array. + + If the new size is greater than maxSize, and the array can grow (grow is + non-zero), then maxSize will be increased to the smallest multiple of grow + greater than or equal to size (ie, maxSize >= size, maxSize % grow == 0). + + Attempting to increase maxSize on an array that cannot grow is an error: + it is assumed that the array struct does not own the backing memory. + + \param array *Address* of the array to be modified (ie, pointer to the + array struct instance, not the instance itself: use & for + static instances of the array struct). + \param newSize The new size of the array: newly opened slots are + uninitialized, but old slots retain their values. + \hideinitializer +*/ +#define DARRAY_RESIZE(array, newSize) \ + do { \ + __auto_type ar = (array); \ + size_t ns = (newSize); \ + if (__builtin_expect (ns > ar->maxSize, 0)) { \ + if (__builtin_expect (!ar->grow, 0)) { \ + Sys_Error ("Attempt to grow fixed-size darray: %s:%d", \ + __FILE__, __LINE__); \ + } \ + ar->maxSize = ar->grow * ((ns + ar->grow - 1) / ar->grow); \ + ar->a = realloc (ar->a, ar->maxSize * sizeof (*ar->a)); \ + if (__builtin_expect (!ar->a, 0)) { \ + Sys_Error ("failed to realloc darray: %s:%d", \ + __FILE__, __LINE__); \ + } \ + } \ + ar->size = ns; \ + } while (0) + +/** Append a value to the end of the array. + + The array is grown by one and the value written to the newly opened slot. + + If the new array size is greater than maxSize and the array can be grown, + the array backing will be resized to the next multiple of grow. + + Attempting to grow an array that cannot grow is an error: it is assumed + that the array struct does not own the backing memory. + + \param array *Address* of the array to be modified (ie, pointer to the + array struct instance, not the instance itself: use & for + static instances of the array struct). + \param value The value to be appended to the array. Must be of a type + compatible with that used for creating the array struct. + \return The appended value: can be assigned to another compatible + value. + \hideinitializer +*/ +#define DARRAY_APPEND(array, value) \ + ({ \ + __auto_type ar = (array); \ + __auto_type ob = (value); \ + size_t sz = ar->size; \ + DARRAY_RESIZE (ar, ar->size + 1); \ + ar->a[sz] = ob; \ + }) + +/** Open a hole in the array for bulk copying of data. + + The array is grown by the requested size, opening a hole at the specified + index. Values beyond the index are copied to just after the newly opened + hole. + + If the new array size is greater than maxSize and the array can be grown, + the array backing will be resized to the next multiple of grow. + + Attempting to grow an array that cannot grow is an error: it is assumed + that the array struct does not own the backing memory. + + \param array *Address* of the array to be modified (ie, pointer to the + array struct instance, not the instance itself: use & for + static instances of the array struct). + \param index The index at which the hole will begin. + \param space The sized of the hole to be opened, in array elements. + \return The *address* of the newly opened hole: can be passed to + memcpy and friends. + + memcpy (DARRAY_OPEN_AT(array, index, size), data, + size * sizeof (*data)); + \hideinitializer +*/ +#define DARRAY_OPEN_AT(array, index, space) \ + ({ \ + __auto_type ar = (array); \ + size_t po = (index); \ + size_t sp = (space); \ + if (__builtin_expect (po > ar->size, 0)) { \ + Sys_Error ("Attempt to insert elements outside darray: " \ + "%s:%d", __FILE__, __LINE__); \ + } \ + DARRAY_RESIZE (ar, ar->size + sp); \ + memmove (&ar->a[po + sp], &ar->a[po], \ + (ar->size - po) * sizeof (*ar->a)); \ + &ar->a[po]; \ + }) + +/** Insert a value into the array at the specified index. + + The array is grown by one at the specified index and the value written + to the newly opened slot. Values beyond the index are copied to just + after the newly opened slot. + + If the new array size is greater than maxSize and the array can be grown, + the array backing will be resized to the next multiple of grow. + + Attempting to grow an array that cannot grow is an error: it is assumed + that the array struct does not own the backing memory. + + Attempting to insert a value beyond one past the end of the array is an + error (inserting at index = size is valid). + + \param array *Address* of the array to be modified (ie, pointer to the + array struct instance, not the instance itself: use & for + static instances of the array struct). + \param value The value to be inserted into the array. Must be of a type + compatible with that used for creating the array struct. + \param index The index at which the value will be inserted + \return The inserted value: can be assigned to another compatible + value. + \hideinitializer +*/ +#define DARRAY_INSERT_AT(array, value, index) \ + ({ \ + __auto_type ar = (array); \ + __auto_type ob = (value); \ + *DARRAY_OPEN_AT (ar, index, 1) = ob; \ + }) + +/** Close a segment of an array. + + The values beyond the segment are copied to the beginning of the segment + and the array size reduced by the size of the segment. All but the first + one of the values previously in the segment are lost and gone forever. + + Attempting to close a segment that extends outside the array is an error. + + \param array *Address* of the array to be modified (ie, pointer to the + array struct instance, not the instance itself: use & for + static instances of the array struct). + \param index The index of the beginning of the segment. + \param count The number of values in the segment. + \return The single value at the beginning of the segment: can be + assigned to another compatible value. + \hideinitializer +*/ +#define DARRAY_CLOSE_AT(array, index, count) \ + ({ \ + __auto_type ar = (array); \ + size_t po = (index); \ + size_t co = (count); \ + if (__builtin_expect (po + co > ar->size \ + || po >= ar->size, 0)) { \ + Sys_Error ("Attempt to remove elements outside darray: " \ + "%s:%d", __FILE__, __LINE__); \ + } \ + __auto_type ob = ar->a[po]; \ + memmove (&ar->a[po], &ar->a[po + co], \ + (ar->size - po - co) * sizeof (ob)); \ + ar->size -= co; \ + ob; \ + }) + +/** Remove a value from an array at the specified index. + + The values beyond the index are moved down to fill the hole left by the + single value and the array size reduced by one. + + Attempting to remove a value from beyond the array is an error. + + \param array *Address* of the array to be modified (ie, pointer to the + array struct instance, not the instance itself: use & for + static instances of the array struct). + \param index The index of the value to be removed. + \return The removed value: can be assigned to another compatible + value. + \hideinitializer +*/ +#define DARRAY_REMOVE_AT(array, index) \ + ({ \ + __auto_type ar = (array); \ + DARRAY_CLOSE_AT (ar, index, 1); \ + }) + +/** Remove (pop) a value from the end of an array. + + The size of the array size reduced by one. + + Attempting to remove a value from an empty array is an error. + + \param array *Address* of the array to be modified (ie, pointer to the + array struct instance, not the instance itself: use & for + static instances of the array struct). + \return The removed value: can be assigned to another compatible + value. + \hideinitializer +*/ +#define DARRAY_REMOVE(array) \ + ({ \ + __auto_type ar = (array); \ + DARRAY_CLOSE_AT (ar, ar->size - 1, 1); \ + }) + +///@} + +#endif//__pr_type_h diff --git a/libs/util/test/Makefile.am b/libs/util/test/Makefile.am index a7bdb5d4e..f6dd4115a 100644 --- a/libs/util/test/Makefile.am +++ b/libs/util/test/Makefile.am @@ -3,8 +3,8 @@ AUTOMAKE_OPTIONS= foreign AM_CPPFLAGS= -I$(top_srcdir)/include check_PROGRAMS= \ - test-bary test-cs test-dq test-half test-mat3 test-mat4 test-plist \ - test-qfs test-quat test-seb test-seg test-set test-vrect + test-bary test-cs test-darray test-dq test-half test-mat3 test-mat4 \ + test-plist test-qfs test-quat test-seb test-seg test-set test-vrect test_bary_SOURCES=test-bary.c test_bary_LDADD=$(top_builddir)/libs/util/libQFutil.la @@ -14,6 +14,10 @@ test_cs_SOURCES=test-cs.c test_cs_LDADD=$(top_builddir)/libs/util/libQFutil.la test_cs_DEPENDENCIES=$(top_builddir)/libs/util/libQFutil.la +test_darray_SOURCES=test-darray.c +test_darray_LDADD=$(top_builddir)/libs/util/libQFutil.la +test_darray_DEPENDENCIES=$(top_builddir)/libs/util/libQFutil.la + test_dq_SOURCES=test-dq.c test_dq_LDADD=$(top_builddir)/libs/util/libQFutil.la test_dq_DEPENDENCIES=$(top_builddir)/libs/util/libQFutil.la diff --git a/libs/util/test/test-darray.c b/libs/util/test/test-darray.c new file mode 100644 index 000000000..59dbad0ab --- /dev/null +++ b/libs/util/test/test-darray.c @@ -0,0 +1,213 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#define remove remove_renamed +#include +#include +#include + +#include "QF/darray.h" +#include "QF/sys.h" +#undef remove + +typedef int (*test_func) (int a, int b); +typedef struct intarray_s DARRAY_TYPE(int) intarray_t; + +intarray_t intarray = {0, 0, 16, 0}; + +static int +append (int val, int b) +{ + return DARRAY_APPEND (&intarray, val); +} + +static int +check_size (int a, int b) +{ + return intarray.size; +} + +static int +check_maxSize (int a, int b) +{ + return intarray.maxSize; +} + +static int +check_grow (int a, int b) +{ + return intarray.grow; +} + +static int +check_maxSizeGrowth (int a, int b) +{ + return intarray.maxSize % intarray.grow; +} + +static int +check_array (int a, int b) +{ + return !!intarray.a; +} + +static int +check_value (int index, int b) +{ + if ((size_t) index >= intarray.size) { + Sys_Error ("indexing beyond array"); + } + return intarray.a[index]; +} + +static int +insert_at (int val, int pos) +{ + return DARRAY_INSERT_AT (&intarray, val, pos); +} + +static int +open_at (int pos, int size) +{ + return DARRAY_OPEN_AT (&intarray, pos, size) - intarray.a; +} + +static const char text[] = "Aloy is an awesome huntress."; +static int +open_at2 (int pos, int size) +{ + memcpy(DARRAY_OPEN_AT (&intarray, pos, size), text, size * sizeof (int)); + return strcmp((char*) (intarray.a + pos), text); +} + +static int +remove_at (int pos, int b) +{ + return DARRAY_REMOVE_AT (&intarray, pos); +} + +static int +remove (int pos, int b) +{ + return DARRAY_REMOVE (&intarray); +} + +static int +close_at (int pos, int size) +{ + return DARRAY_CLOSE_AT (&intarray, pos, size); +} + +static int +clear (int a, int b) +{ + DARRAY_CLEAR (&intarray); + return 0; +} + +static int +resize (int size, int b) +{ + DARRAY_RESIZE (&intarray, size); + return 0; +} + +struct { + test_func test; + int param1, param2; + int test_expect; +} tests[] = { + {check_size, 0, 0, 0}, // confirm array empty but can grow + {check_maxSize, 0, 0, 0}, + {check_grow, 0, 0, 16}, + {check_array, 0, 0, 0}, + {append, 5, 0, 5}, // test first append to emtpty array + {check_size, 0, 0, 1}, + {check_maxSizeGrowth, 0, 0, 0}, + {check_maxSize, 0, 0, 16}, + {check_array, 0, 0, 1}, + {check_value, 0, 0, 5}, + {append, 42, 0, 42}, // test a second append + {check_size, 0, 0, 2}, + {check_maxSize, 0, 0, 16}, + {check_value, 0, 0, 5}, + {check_value, 1, 0, 42}, + {insert_at, 69, 1, 69}, // test insertions + {insert_at, 96, 0, 96}, + {check_size, 0, 0, 4}, + {check_maxSize, 0, 0, 16}, + {check_value, 0, 0, 96}, + {check_value, 1, 0, 5}, + {check_value, 2, 0, 69}, + {check_value, 3, 0, 42}, + {open_at, 2, 14, 2}, // test opening a large hole + {check_maxSizeGrowth, 0, 0, 0}, + {check_maxSize, 0, 0, 32}, + {check_size, 0, 0, 18}, + {check_value, 0, 0, 96}, + {check_value, 1, 0, 5}, + {check_value, 16, 0, 69}, + {check_value, 17, 0, 42}, + {close_at, 1, 15, 5}, // test block removal + {check_maxSize, 0, 0, 32}, + {check_size, 0, 0, 3}, + {check_value, 0, 0, 96}, + {check_value, 1, 0, 69}, + {check_value, 2, 0, 42}, + {remove, 0, 0, 42}, // test "pop" + {check_maxSize, 0, 0, 32}, + {check_size, 0, 0, 2}, + {check_value, 0, 0, 96}, + {check_value, 1, 0, 69}, + {remove_at, 0, 0, 96}, // test remove at + {check_maxSize, 0, 0, 32}, + {check_size, 0, 0, 1}, + {check_value, 0, 0, 69}, + {insert_at, 71, 1, 71}, // test insertion at end + {resize, 48, 0, 0}, // test resize bigger + {check_maxSizeGrowth, 0, 0, 0}, + {check_maxSize, 0, 0, 48}, + {check_size, 0, 0, 48}, + {check_value, 0, 0, 69}, + {check_value, 1, 0, 71}, + {resize, 24, 0, 0}, // test resize smaller + {check_maxSizeGrowth, 0, 0, 0}, + {check_maxSize, 0, 0, 48}, + {check_size, 0, 0, 24}, + {check_value, 0, 0, 69}, + {check_value, 1, 0, 71}, + {open_at2, 1, (sizeof (text) + sizeof (int) - 1) / sizeof (int), 0}, + {check_value, 0, 0, 69}, + {check_value, 1, 0, 0x796f6c41}, + {check_value, 9, 0, 71}, + {clear, 0, 0, 0}, // test clearing + {check_size, 0, 0, 0}, + {check_maxSize, 0, 0, 0}, + {check_array, 0, 0, 0}, +}; +#define num_tests (sizeof (tests) / sizeof (tests[0])) +int test_start_line = __LINE__ - num_tests - 2; + +int +main (int argc, const char **argv) +{ + int res = 0; + + size_t i; + + for (i = 0; i < num_tests; i++) { + if (tests[i].test) { + int test_res; + test_res = tests[i].test (tests[i].param1, tests[i].param2); + if (test_res != tests[i].test_expect) { + res |= 1; + printf ("test %d (line %d) failed\n", (int) i, + (int) i + test_start_line); + printf ("expect: %d\n", tests[i].test_expect); + printf ("got : %d\n", test_res); + continue; + } + } + } + return res; +}