From b87906bb49e190ac35e2030cb2bd26ed41f0118c Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 11 Feb 2022 00:28:28 -0800 Subject: [PATCH] Way overcomplicated. --- src/array.h | 396 +++++++++++++++++ src/array_coda.h | 437 +++++++++++++++++++ src/heap.h | 432 +++++++++++++++++++ src/interpret.re_c.c | 106 ++++- src/list.h | 434 +++++++++++++++++++ src/list_coda.h | 406 ++++++++++++++++++ src/pool.h | 378 +++++++++++++++++ src/table.h | 988 +++++++++++++++++++++++++++++++++++++++++++ src/to_string.h | 156 +++++++ 9 files changed, 3713 insertions(+), 20 deletions(-) create mode 100644 src/array.h create mode 100644 src/array_coda.h create mode 100644 src/heap.h create mode 100644 src/list.h create mode 100644 src/list_coda.h create mode 100644 src/pool.h create mode 100644 src/table.h create mode 100644 src/to_string.h diff --git a/src/array.h b/src/array.h new file mode 100644 index 0000000..a8c4df9 --- /dev/null +++ b/src/array.h @@ -0,0 +1,396 @@ +/** @license 2016 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @abstract Source ; examples . + + @subtitle Contiguous dynamic array + + ![Example of array.](../web/array.png) + + array> is a dynamic array that stores contiguous type>. + Resizing may be necessary when increasing the size of the array; this incurs + amortised cost, and any pointers to this memory may become stale. + + @param[ARRAY_NAME, ARRAY_TYPE] + `` that satisfies `C` naming conventions when mangled and a valid tag-type, + type>, associated therewith; required. `` is private, whose + names are prefixed in a manner to avoid collisions. + + @param[ARRAY_CODA] + Include more functions contained in , where `` is + `array`. + + @param[ARRAY_MIN_CAPACITY] + Default is 3; optional number in `[2, SIZE_MAX]` that the capacity can not go + below. + + @param[ARRAY_EXPECT_TRAIT] + Do not un-define certain variables for subsequent inclusion in a parameterized + trait. + + @param[ARRAY_COMPARE_NAME, ARRAY_COMPARE, ARRAY_IS_EQUAL] + Compare trait contained in . An optional mangled name for + uniqueness and a function implementing either compare_fn> or + bipredicate_fn>. + + @param[ARRAY_TO_STRING_NAME, ARRAY_TO_STRING] + To string trait contained in . An optional mangled name for + uniqueness and function implementing to_string_fn>. + + @std C89 */ + +#if !defined(ARRAY_NAME) || !defined(ARRAY_TYPE) +#error Name ARRAY_NAME or tag type ARRAY_TYPE undefined. +#endif +#if defined(ARRAY_TO_STRING_NAME) || defined(ARRAY_TO_STRING) +#define ARRAY_TO_STRING_TRAIT 1 +#else +#define ARRAY_TO_STRING_TRAIT 0 +#endif +#if defined(ARRAY_COMPARE_NAME) || defined(ARRAY_COMPARE) \ + || defined(ARRAY_IS_EQUAL) +#define ARRAY_COMPARE_TRAIT 1 +#else +#define ARRAY_COMPARE_TRAIT 0 +#endif +#define ARRAY_TRAITS ARRAY_TO_STRING_TRAIT + ARRAY_COMPARE_TRAIT +#if ARRAY_TRAITS > 1 +#error Only one trait per include is allowed; use ARRAY_EXPECT_TRAIT. +#endif +#if defined(ARRAY_TO_STRING_NAME) && !defined(ARRAY_TO_STRING) +#error ARRAY_TO_STRING_NAME requires ARRAY_TO_STRING. +#endif +#if defined(ARRAY_COMPARE_NAME) \ + && (!(!defined(ARRAY_COMPARE) ^ !defined(ARRAY_IS_EQUAL))) +#error ARRAY_COMPARE_NAME requires ARRAY_COMPARE or ARRAY_IS_EQUAL not both. +#endif + +#ifndef ARRAY_H /* */ + + +#if ARRAY_TRAITS == 0 /* */ + +/** A valid tag type set by `ARRAY_TYPE`. */ +typedef ARRAY_TYPE PA_(type); + +/** Manages the array field `data` which has `size` elements. The space is + indexed up to `capacity`, which is at least `size`. To initialize it to an + idle state, see array>, `ARRAY_IDLE`, `{0}` (`C99`,) or being `static`. + The fields should be treated as read-only; any modification is liable to cause + the array to go into an invalid state. + + ![States.](../web/states.png) */ +struct A_(array) { PA_(type) *data; size_t size, capacity; }; +/* !data -> !size, data -> capacity >= min && size <= capacity <= max */ + +/** Initialises `a` to idle. @order \Theta(1) @allow */ +static void A_(array)(struct A_(array) *const a) + { assert(a), a->data = 0, a->capacity = a->size = 0; } + +/** Destroys `a` and returns it to idle. @allow */ +static void A_(array_)(struct A_(array) *const a) + { assert(a), free(a->data), A_(array)(a); } + +/** Ensures `min` capacity of `a`. Invalidates pointers in `a`. @param[min] If + zero, does nothing. @return Success; otherwise, `errno` will be set. + @throws[ERANGE] Tried allocating more then can fit in `size_t` or `realloc` + doesn't follow POSIX. @throws[realloc] @allow */ +static int A_(array_reserve)(struct A_(array) *const a, const size_t min) { + size_t c0; + PA_(type) *data; + const size_t max_size = (size_t)-1 / sizeof *a->data; + assert(a); + if(a->data) { + assert(a->size <= a->capacity); + if(min <= a->capacity) return 1; + c0 = a->capacity < ARRAY_MIN_CAPACITY + ? ARRAY_MIN_CAPACITY : a->capacity; + } else { /* Idle. */ + assert(!a->size && !a->capacity); + if(!min) return 1; + c0 = ARRAY_MIN_CAPACITY; + } + if(min > max_size) return errno = ERANGE, 0; + /* `c_n = a1.625^n`, approximation golden ratio `\phi ~ 1.618`. */ + while(c0 < min) { /* \O(\log min), in practice, negligible. */ + size_t c1 = c0 + (c0 >> 1) + (c0 >> 3); + if(c0 >= c1) { c0 = max_size; break; } /* Unlikely. */ + c0 = c1; + } + if(!(data = realloc(a->data, sizeof *a->data * c0))) + { if(!errno) errno = ERANGE; return 0; } + a->data = data, a->capacity = c0; + return 1; +} + +/** The capacity of `a` will be increased to at least `n` elements beyond the + size. Invalidates any pointers in `a`. + @return The start of the buffered space at the back of the array. If `a` is + idle and `buffer` is zero, a null pointer is returned, otherwise null + indicates an error. @throws[realloc, ERANGE] @allow */ +static PA_(type) *A_(array_buffer)(struct A_(array) *const a, const size_t n) { + assert(a); + if(a->size > (size_t)-1 - n) { errno = ERANGE; return 0; } + return A_(array_reserve)(a, a->size + n) && a->data ? a->data + a->size : 0; +} + +/** Appends `n` items on the back of `a`. This is used in the coda and + array_append>. */ +static PA_(type) *PA_(append)(struct A_(array) *const a, const size_t n) { + PA_(type) *b; + assert(a); + if(!(b = A_(array_buffer)(a, n))) return 0; + assert(n <= a->capacity && a->size <= a->capacity - n); + return a->size += n, b; +} + +/** Adds `n` un-initialised elements at position `at` in `a`. The buffer holds + enough elements or it will invalidate any pointers in `a`. + @param[at] A number smaller than or equal to `a.size`; if `a.size`, this + function behaves as array_append>. + @return A pointer to the start of the new region, where there are `n` + elements. @throws[realloc, ERANGE] @allow */ +static PA_(type) *A_(array_insert)(struct A_(array) *const a, + const size_t n, const size_t at) { + const size_t old_size = a->size; + PA_(type) *const b = PA_(append)(a, n); + assert(a && at <= old_size); + if(!b) return 0; + memmove(a->data + at + n, a->data + at, sizeof *a->data * (old_size - at)); + return a->data + at; +} + +/** @return Adds (push back) one new element of `a`. The buffer holds an + element or it will invalidate pointers in `a`. + @order amortised \O(1) @throws[realloc, ERANGE] @allow */ +static PA_(type) *A_(array_new)(struct A_(array) *const a) + { return PA_(append)(a, 1); } + +/** Shrinks the capacity `a` to the size, freeing unused memory. If the size is + zero, it will be in an idle state. Invalidates pointers in `a`. + @return Success. @throws[ERANGE, realloc] Unlikely `realloc` error. */ +static int A_(array_shrink)(struct A_(array) *const a) { + PA_(type) *data; + size_t c; + assert(a && a->capacity >= a->size); + if(!a->data) return assert(!a->size && !a->capacity), 1; + c = a->size && a->size > ARRAY_MIN_CAPACITY ? a->size : ARRAY_MIN_CAPACITY; + if(!(data = realloc(a->data, sizeof *a->data * c))) + { if(!errno) errno = ERANGE; return 0; } + a->data = data, a->capacity = c; + return 1; +} + +/** Removes `datum` from `a`. @order \O(`a.size`). @allow */ +static void A_(array_remove)(struct A_(array) *const a, + PA_(type) *const datum) { + const size_t n = (size_t)(datum - a->data); + assert(a && datum && datum >= a->data && datum < a->data + a->size); + memmove(datum, datum + 1, sizeof *datum * (--a->size - n)); +} + +/** Removes `datum` from `a` and replaces it with the tail. + @order \O(1). @allow */ +static void A_(array_lazy_remove)(struct A_(array) *const a, + PA_(type) *const datum) { + size_t n = (size_t)(datum - a->data); + assert(a && datum && datum >= a->data && datum < a->data + a->size); + if(--a->size != n) memcpy(datum, a->data + a->size, sizeof *datum); +} + +/** Sets `a` to be empty. That is, the size of `a` will be zero, but if it was + previously in an active non-idle state, it continues to be. + @order \Theta(1) @allow */ +static void A_(array_clear)(struct A_(array) *const a) + { assert(a), a->size = 0; } + +/** @return The last element or null if `a` is empty. @order \Theta(1) @allow */ +static PA_(type) *A_(array_peek)(const struct A_(array) *const a) + { return assert(a), a->size ? a->data + a->size - 1 : 0; } + +/** @return Value from the the top of `a` that is removed or null if the array + is empty. @order \Theta(1) @allow */ +static PA_(type) *A_(array_pop)(struct A_(array) *const a) + { return assert(a), a->size ? a->data + --a->size : 0; } + +/** Adds `n` elements to the back of `a`. It will invalidate pointers in `a` if + `n` is greater than the buffer space. + @return A pointer to the elements. If `a` is idle and `n` is zero, a null + pointer will be returned, otherwise null indicates an error. + @throws[realloc, ERANGE] @allow */ +static PA_(type) *A_(array_append)(struct A_(array) *const a, const size_t n) + { return PA_(append)(a, n); } + +/** Indices [`i0`, `i1`) of `a` will be replaced with a copy of `b`. + @param[b] Can be null, which acts as empty, but cannot be `a`. + @return Success. @throws[realloc, ERANGE] @allow */ +static int A_(array_splice)(/*restrict*/ struct A_(array) *const a, + /*restrict*/ const struct A_(array) *const b, + const size_t i0, const size_t i1) { + const size_t a_range = i1 - i0, b_range = b ? b->size : 0; + assert(a && a != b && i0 <= i1 && i1 <= a->size); + if(a_range < b_range) { /* The output is bigger. */ + const size_t diff = b_range - a_range; + if(!A_(array_buffer)(a, diff)) return 0; + memmove(a->data + i1 + diff, a->data + i1, + (a->size - i1) * sizeof *a->data); + a->size += diff; + } else if(b_range < a_range) { /* The output is smaller. */ + memmove(a->data + i0 + b_range, a->data + i1, + (a->size - i1) * sizeof *a->data); + a->size -= a_range - b_range; + } + if(b) memcpy(a->data + i0, b->data, b->size * sizeof *a->data); + return 1; +} + +/* */ + +/* */ + +#ifdef ARRAY_CODA /* */ + +#ifdef ARRAY_TEST /* */ + +static void PA_(unused_base_coda)(void); +static void PA_(unused_base)(void) + { A_(array_)(0); A_(array_insert)(0, 0, 0); A_(array_new)(0); + A_(array_shrink)(0); A_(array_remove)(0, 0); A_(array_lazy_remove)(0, 0); + A_(array_clear)(0); A_(array_peek)(0); A_(array_pop)(0); + A_(array_append)(0, 0); A_(array_splice)(0, 0, 0, 0); + PA_(begin)(0, 0); PA_(next)(0); PA_(id)(0); PA_(id_c)(0); + PA_(unused_base_coda)(); } +static void PA_(unused_base_coda)(void) { PA_(unused_base)(); } + + +#elif defined(ARRAY_TO_STRING) /* base code --> */ +#undef SZ_ +#undef ARRAY_TO_STRING +#ifdef ARRAY_TO_STRING_NAME +#undef ARRAY_TO_STRING_NAME +#endif + + +#else /* to string trait --> */ +#include "array_coda.h" /* (Already included.) */ +#ifdef ARRAY_TEST /* */ +#undef ACC_ +#undef PACC_ +#undef ARRAY_CODA_NAME +#ifdef ARRAY_COMPARE_NAME +#undef ARRAY_COMPARE_NAME +#endif +#ifdef ARRAY_COMPARE +#undef ARRAY_COMPARE +#endif +#ifdef ARRAY_IS_EQUAL +#undef ARRAY_IS_EQUAL +#endif + + +#endif /* traits --> */ + + +#ifdef ARRAY_EXPECT_TRAIT /* */ +#undef ARRAY_TO_STRING_TRAIT +#undef ARRAY_COMPARE_TRAIT +#undef ARRAY_TRAITS diff --git a/src/array_coda.h b/src/array_coda.h new file mode 100644 index 0000000..a9c8bfe --- /dev/null +++ b/src/array_coda.h @@ -0,0 +1,437 @@ +/* @license 2020 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @subtitle Array coda + + This defines an optional set of functions that is nice, for any child of + `array` not providing additional constraints. (Thus, `array`?) + + @param[ARRAY_CODA_TYPE] + Type of array. + + @param[ARRAY_CODA_BOX_TO_C, ARRAY_CODA_BOX_TO] + Function picking out the array satisfying box_to_array_c> and + box_to_array>. + + @param[AC_] + A one-argument macro producing a name that is responsible for the name of the + functions. Should be something like `AC_(x) -> foo_widget_x`. The caller is + responsible for undefining `AC_`. + + @std C89 */ + +#if !defined(BOX_) || !defined(BOX_CONTAINER) || !defined(BOX_CONTENTS) \ + || !defined(ARRAY_CODA_TYPE) || !defined(ARRAY_CODA_BOX_TO_C) \ + || !defined(ARRAY_CODA_BOX_TO) || !defined(AC_) \ + || defined(BOX_IS_EQUAL) && defined(BOX_COMPARE) +#error Unexpected preprocessor symbols. +#endif + +#ifndef ARRAY_CODA_H /* */ + +#ifndef ARRAY_CODA_ONCE /* */ + + +#if !defined(BOX_IS_EQUAL) && !defined(BOX_COMPARE) /* */ + +#ifdef ARRAY_CODA_NAME +#define ACC_(n) AC_(ARRAY_CAT(ARRAY_CODA_NAME, n)) +#else /* name --> */ +#define PACC_(n) ARRAY_CAT(array_coda, ACC_(n)) + +#ifdef BOX_COMPARE /* */ + +/** @return If `a` piecewise equals `b`, which both can be null. + @order \O(`size`) @allow */ +static int ACC_(is_equal)(const PAC_(box) *const a, const PAC_(box) *const b) +{ + const PAC_(array) *aa, *bb; + const PAC_(type) *ad, *bd, *end; + if(!a) return !b; + if(!b) return 0; + aa = PAC_(b2a_c)(a), bb = PAC_(b2a_c)(a), assert(aa && bb); + if(aa->size != bb->size) return 0; + for(ad = aa->data, bd = bb->data, end = ad + aa->size; ad < end; ad++, bd++) + if(!PACC_(is_equal)(ad, bd)) return 0; + return 1; +} + +/** : Removes consecutive duplicate elements in `box`. + @param[merge] Controls surjection. Called with duplicate elements, if false + `(x, y)->(x)`, if true `(x,y)->(y)`. More complex functions, `(x, y)->(x+y)` + can be simulated by mixing the two in the value returned. Can be null: behaves + like false. @order \O(`a.size` \times `merge`) @allow */ +static void ACC_(unique_merge)(PAC_(box) *const box, + const PAC_(biaction_fn) merge) { + PAC_(array) *a = PAC_(b2a)(box); + size_t target, from, cursor, choice, next, move; + const size_t last = a->size; + int is_first, is_last; + assert(box && a); + for(target = from = cursor = 0; cursor < last; cursor += next) { + /* Bijective `[from, cursor)` is moved lazily. */ + for(choice = 0, next = 1; cursor + next < last && PAC_(is_equal)(a->data + + cursor + choice, a->data + cursor + next); next++) + if(merge && merge(a->data + choice, a->data + next)) choice = next; + if(next == 1) continue; + /* Must move injective `cursor + choice \in [cursor, cursor + next)`. */ + is_first = !choice; + is_last = (choice == next - 1); + move = cursor - from + (size_t)is_first; + memmove(a->data + target, a->data + from, sizeof *a->data * move), + target += move; + if(!is_first && !is_last) memcpy(a->data + target, + a->data + cursor + choice, sizeof *a->data), target++; + from = cursor + next - (size_t)is_last; + } + /* Last differed move. */ + move = last - from; + memmove(a->data + target, a->data + from, sizeof *a->data * move), + target += move, assert(a->size >= target); + a->size = target; +} + +/** : Removes consecutive duplicate elements in `a`. + @order \O(`a.size`) @allow */ +static void ACC_(unique)(PAC_(box) *const a) { ACC_(unique_merge)(a, 0); } + +static void PACC_(unused_compare_coda)(void); +static void PACC_(unused_compare)(void) { +#ifdef BOX_COMPARE /* */ + ACC_(is_equal)(0, 0); ACC_(unique_merge)(0, 0); ACC_(unique)(0); + PACC_(unused_compare_coda)(); } +static void PACC_(unused_compare_coda)(void) { PACC_(unused_compare)(); } + +#ifdef BOX_COMPARE +#undef BOX_COMPARE +#endif +#ifdef BOX_IS_EQUAL +#undef BOX_IS_EQUAL +#endif +#ifdef BOX_COMPARE_NAME +#undef BOX_COMPARE_NAME +#endif +/*#undef AC_C_ +#undef PACC_ Need for tests. */ + +#endif /* compare/is equal --> */ diff --git a/src/heap.h b/src/heap.h new file mode 100644 index 0000000..cdbe175 --- /dev/null +++ b/src/heap.h @@ -0,0 +1,432 @@ +/** @license 2020 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @abstract Source , depends on ; examples + . + + @subtitle Priority-queue + + ![Example of heap.](../web/heap.png) + + A heap> is a binary heap, proposed by + using terminology of + . It can be used as an implementation of a priority + queue; internally, it is a `<node>array` with implicit heap properties on + priority> and an optional value> pointer value. + + @param[HEAP_NAME, HEAP_TYPE] + `` that satisfies `C` naming conventions when mangled and an assignable + type priority> associated therewith. `HEAP_NAME` is required; + `HEAP_TYPE` defaults to `unsigned int`. `` is private, whose names are + prefixed in a manner to avoid collisions. + + @param[HEAP_COMPARE] + A function satisfying compare_fn>. Defaults to minimum-hash. + Required if `HEAP_TYPE` is changed to an incomparable type. + + @param[HEAP_VALUE] + Optional value value>, that is stored as a reference in + heapnode>; declaring it is sufficient. If set, has no effect on the + ranking, but affects value>, (otherwise, it's the same field as + priority>.) + + @param[HEAP_EXPECT_TRAIT] + Do not un-define certain variables for subsequent inclusion in a parameterized + trait. + + @param[HEAP_TO_STRING_NAME, HEAP_TO_STRING] + To string trait contained in ; an optional unique `` + that satisfies `C` naming conventions when mangled and function implementing + to_string_fn>. + + @depend [array](https://github.com/neil-edelman/array) + @std C89 + @fixme Add decrease priority. + @fixme Add replace. + @fixme `HEAP_VALUE` has to be a pointer; use `memcpy` instead. */ + +#ifndef HEAP_NAME +#error Generic HEAP_NAME undefined. +#endif +#if defined(HEAP_TO_STRING_NAME) || defined(HEAP_TO_STRING) /* */ +#define HEAP_TRAITS HEAP_TO_STRING_TRAIT +#if HEAP_TRAITS > 1 +#error Only one trait per include is allowed; use HEAP_EXPECT_TRAIT. +#endif +#if defined(HEAP_TO_STRING_NAME) && !defined(HEAP_TO_STRING) +#error HEAP_TO_STRING_NAME requires HEAP_TO_STRING. +#endif + +#ifndef HEAP_H /* */ + + +#if HEAP_TRAITS == 0 /* */ +/* Check that `HEAP_COMPARE` is a function implementing + compare_fn>, if defined. */ +static const PH_(compare_fn) PH_(compare) = (HEAP_COMPARE); + +#ifdef HEAP_VALUE /* */ + +/* This relies on `array.h` which must be in the same directory. */ +#define ARRAY_NAME PH_(node) +#define ARRAY_TYPE PH_(node) +#include "array.h" + +/** Stores the heap as an implicit binary tree in an array called `a`. To + initialize it to an idle state, see heap>, `HEAP_IDLE`, `{0}` (`C99`), + or being `static`. + + ![States.](../web/states.png) */ +struct H_(heap) { struct PH_(node_array) a; }; + +/** Extracts the priority> of `node`, which must not be null. */ +static PH_(priority) PH_(get_priority)(const PH_(node) *const node) { +#ifdef HEAP_VALUE + return node->priority; +#else + return *node; +#endif +} + +/** Extracts the value> of `node`, which must not be null. */ +static PH_(value) PH_(get_value)(const PH_(node) *const node) { +#ifdef HEAP_VALUE /* <-- value */ + return node->value; +#else /* value --> */ +} + +/** Copies `src` to `dest`. */ +static void PH_(copy)(const PH_(node) *const src, PH_(node) *const dest) { +#ifdef HEAP_VALUE /* */ +} + +/** Find the spot in `heap` where `node` goes and put it there. + @param[heap] At least one entry; the last entry will be replaced by `node`. + @order \O(log `size`) */ +static void PH_(sift_up)(struct H_(heap) *const heap, PH_(node) *const node) { + PH_(node) *const n0 = heap->a.data; + PH_(priority) p = PH_(get_priority)(node); + size_t i = heap->a.size - 1; + assert(heap && heap->a.size && node); + if(i) { + size_t i_up; + do { /* Note: don't change the `<=`; it's a queue. */ + i_up = (i - 1) >> 1; + if(PH_(compare)(PH_(get_priority)(n0 + i_up), p) <= 0) break; + PH_(copy)(n0 + i_up, n0 + i); + } while((i = i_up)); + } + PH_(copy)(node, n0 + i); +} + +/** Pop the head of `heap` and restore the heap by sifting down the last + element. @param[heap] At least one entry. The head is popped, and the size + will be one less. */ +static void PH_(sift_down)(struct H_(heap) *const heap) { + const size_t size = (assert(heap && heap->a.size), --heap->a.size), + half = size >> 1; + size_t i = 0, c; + PH_(node) *const n0 = heap->a.data, + *const down = n0 + size /* Put it at the top. */, *child; + const PH_(priority) down_p = PH_(get_priority)(down); + while(i < half) { + c = (i << 1) + 1; + if(c + 1 < size && PH_(compare)(PH_(get_priority)(n0 + c), + PH_(get_priority)(n0 + c + 1)) > 0) c++; + child = n0 + c; + if(PH_(compare)(down_p, PH_(get_priority)(child)) <= 0) break; + PH_(copy)(child, n0 + i); + i = c; + } + PH_(copy)(down, n0 + i); +} + +/** Restore the `heap` by permuting the elements so `i` is in the proper place. + This reads from the an arbitrary leaf-node into a temporary value, so is + slightly more complex than sift_down>, but the same thing. + @param[heap] At least `i + 1` entries. */ +static void PH_(sift_down_i)(struct H_(heap) *const heap, size_t i) { + const size_t size = (assert(heap && i < heap->a.size), heap->a.size), + half = size >> 1; + size_t c; + PH_(node) *const n0 = heap->a.data, *child, temp; + int temp_valid = 0; + while(i < half) { + c = (i << 1) + 1; + if(c + 1 < size && PH_(compare)(PH_(get_priority)(n0 + c), + PH_(get_priority)(n0 + c + 1)) > 0) c++; + child = n0 + c; + if(temp_valid) { + if(PH_(compare)(PH_(get_priority)(&temp), + PH_(get_priority)(child)) <= 0) break; + } else { + /* Only happens on the first compare when `i` is in it's original + position. */ + if(PH_(compare)(PH_(get_priority)(n0 + i), + PH_(get_priority)(child)) <= 0) break; + PH_(copy)(n0 + i, &temp), temp_valid = 1; + } + PH_(copy)(child, n0 + i); + i = c; + } + if(temp_valid) PH_(copy)(&temp, n0 + i); +} + +/** Create a `heap` from an array. @order \O(`heap.size`) */ +static void PH_(heapify)(struct H_(heap) *const heap) { + size_t i; + assert(heap); + if(heap->a.size > 1) + for(i = heap->a.size / 2 - 1; (PH_(sift_down_i)(heap, i), i); i--); +} + +/** Removes from `heap`. Must have a non-zero size. */ +static PH_(node) PH_(remove)(struct H_(heap) *const heap) { + const PH_(node) result = *heap->a.data; + assert(heap); + if(heap->a.size > 1) { + PH_(sift_down)(heap); + } else { + assert(heap->a.size == 1); + heap->a.size = 0; + } + return result; +} + +/** Initializes `heap` to be idle. @order \Theta(1) @allow */ +static void H_(heap)(struct H_(heap) *const heap) + { assert(heap), PH_(node_array)(&heap->a); } + +/** Returns `heap` to the idle state where it takes no dynamic memory. + @order \Theta(1) @allow */ +static void H_(heap_)(struct H_(heap) *const heap) + { assert(heap), PH_(node_array_)(&heap->a); } + +/** Sets `heap` to be empty. That is, the size of `heap` will be zero, but if + it was previously in an active non-idle state, it continues to be. + @param[heap] If null, does nothing. @order \Theta(1) @allow */ +static void H_(heap_clear)(struct H_(heap) *const heap) + { assert(heap), PH_(node_array_clear)(&heap->a); } + +/** If the `heap` requires differentiation between empty and zero. (One may + access it directly at `!heap.a.size`.) @return If the heap is empty. @allow */ +static int H_(heap_is_empty)(const struct H_(heap) *const heap) + { return assert(heap), !heap->a.size; } + +/** Copies `node` into `heap`. + @return Success. @throws[ERANGE, realloc] @order \O(log `heap.size`) @allow */ +static int H_(heap_add)(struct H_(heap) *const heap, PH_(node) node) { + assert(heap); + return PH_(node_array_new)(&heap->a) && (PH_(sift_up)(heap, &node), 1); +} + +/** @return The lowest element in `heap` according to `HEAP_COMPARE` or + null/zero if the heap is empty. On some heaps, one may have to call + heap_is_empty> in order to differentiate. @order \O(1) @allow */ +static PH_(value) H_(heap_peek)(const struct H_(heap) *const heap) + { return assert(heap), heap->a.size ? PH_(get_value)(heap->a.data) : 0; } + +/** Remove the lowest element in `heap` according to `HEAP_COMPARE`. + @return The same as heap_peek>. @order \O(log `size`) @allow */ +static PH_(value) H_(heap_pop)(struct H_(heap) *const heap) { + PH_(node) n; + return assert(heap), heap->a.size + ? (n = PH_(remove)(heap), PH_(get_value)(&n)) : 0; +} + +/** The capacity of `heap` will be increased to at least `n` elements beyond + the size. Invalidates pointers in `heap.a`. All the elements in `heap.a.size` + are part of the heap, but `heap.a.size` <= `index` < `heap.a.capacity` + can be used to construct new elements without immediately making them part of + the heap, then heap_append>. + @return The start of the buffered space. If `a` is idle and `buffer` is zero, + a null pointer is returned, otherwise null indicates an error. + @throws[realloc, ERANGE] @allow */ +static PH_(node) *H_(heap_buffer)(struct H_(heap) *const heap, + const size_t n) { return PH_(node_array_buffer)(&heap->a, n); } + +/** Adds and heapifies `n` elements to `heap`. Uses to + sift-down all the internal nodes of heap. The heap elements must exist, see + heap_buffer>. + @param[n] If zero, returns true without heapifying. + @return Success. @order \O(`heap.size` + `n`) @allow */ +static void H_(heap_append)(struct H_(heap) *const heap, const size_t n) { + PH_(node) *more; + /* In practice, pushing uninitialized elements onto the heap does not make + sense, so we assert that the elements exist first. */ + assert(heap && n <= heap->a.capacity - heap->a.size); + more = PH_(node_array_append)(&heap->a, n), assert(more); + if(n) PH_(heapify)(heap); +} + +/** Shallow-copies and heapifies `master` into `heap`. + @param[master] If null, does nothing. @return Success. + @order \O(`heap.size` + `copy.size`) @throws[ERANGE, realloc] @allow */ +static int H_(heap_affix)(struct H_(heap) *const heap, + const struct H_(heap) *const master) { + PH_(node) *n; + assert(heap); + if(!master || !master->a.size) return 1; + assert(master->a.data); + if(!(n = PH_(node_array_buffer)(&heap->a, master->a.size))) return 0; + memcpy(n, master->a.data, sizeof *n * master->a.size); + n = PH_(node_array_append)(&heap->a, master->a.size), assert(n); + PH_(heapify)(heap); + return 1; +} + +/* */ + +/* Define these for traits. */ +#define BOX_ PH_ +#define BOX_CONTAINER struct H_(heap) +#define BOX_CONTENTS PH_(node) + +#ifdef HEAP_TEST /* */ + +static void PH_(unused_base_coda)(void); +static void PH_(unused_base)(void) { + PH_(node) unused; memset(&unused, 0, sizeof unused); + H_(heap)(0); H_(heap_)(0); H_(heap_clear)(0); H_(heap_is_empty)(0); + H_(heap_add)(0, unused); H_(heap_peek)(0); H_(heap_pop)(0); + H_(heap_buffer)(0, 0); H_(heap_append)(0, 0); H_(heap_affix)(0, 0); + PH_(begin)(0, 0); PH_(next)(0); PH_(unused_base_coda)(); +} +static void PH_(unused_base_coda)(void) { PH_(unused_base)(); } + + +#elif defined(HEAP_TO_STRING) /* base code --> */ +#define TSZ_(n) HEAP_CAT(heap_sz, SZ_(n)) +#ifdef HEAP_VALUE /* */ +#include "to_string.h" /** \include */ +#ifdef HEAP_TEST /* */ +#undef TSZ_ +#undef SZ_ +#undef HEAP_TO_STRING +#ifdef HEAP_TO_STRING_NAME +#undef HEAP_TO_STRING_NAME +#endif + +static void PH_(unused_to_string_coda)(void); +static void PH_(unused_to_string)(void) { H_(heap_to_string)(0); + PH_(unused_to_string_coda)(); } +static void PH_(unused_to_string_coda)(void) { PH_(unused_to_string)(); } + + +#endif /* traits --> */ + + +#ifdef HEAP_EXPECT_TRAIT /* */ +#endif /* !trait --> */ +#undef HEAP_TO_STRING_TRAIT +#undef HEAP_TRAITS diff --git a/src/interpret.re_c.c b/src/interpret.re_c.c index c1479a5..722df10 100644 --- a/src/interpret.re_c.c +++ b/src/interpret.re_c.c @@ -82,38 +82,104 @@ scan: */ } -static int parse_uint(const char *s, const char *e, unsigned *u) { - uint32_t n = 0; - for ( ; s < e; ++s) { - unsigned digit = (unsigned)(*s - '0'); - assert(digit < 10); - if(n > (UINT_MAX - digit) / 10) return errno = ERANGE, 0; /* check */ - n = n * 10 + digit; - } - *u = n; - return 1; -} +/*#define POOL_NAME char +#define POOL_TYPE char +#include "../src/pool.h"*/ -static int parse_double(const char *s0, const char *s1, double *d) { - char *finish; - assert(d && s0 && s0 < s1); - *d = strtod(s0, &finish); - return !errno && ((finish == s1) || (errno = EDOM, 0)); +struct year_listlink; +static int year_link_compare(const struct year_listlink *, + const struct year_listlink *); +static void yearlist_to_string(const struct year_listlink *, char (*)[12]); +#define LIST_NAME year +#define LIST_EXPECT_TRAIT +#include "../src/list.h" +#define LIST_COMPARE &year_link_compare +#define LIST_EXPECT_TRAIT +#include "../src/list.h" +#define LIST_TO_STRING &yearlist_to_string +#include "../src/list.h" +struct year { + struct year_listlink link; + int year; + char as_string[8]; +}; +static const size_t year_string_size = sizeof ((struct year *)0)->as_string; +static int year_link_compare(const struct year_listlink *const al, + const struct year_listlink *const bl) { + const struct year *const a = (const struct year *)al, + *const b = (const struct year *)bl; + return (b->year < a->year) - (a->year < b->year); } +#define POOL_NAME year +#define POOL_TYPE struct year +#include "../src/pool.h" +static unsigned hash_year(const struct year *const year) + { return (unsigned)(year->year - INT_MIN); } +static int year_is_equal(const struct year *const a, const struct year *const b) + { return a->year == b->year; } +static void year_to_string(const struct year *const y, char (*const a)[12]) + { strncpy(*a, y->as_string, sizeof(*a) - 1), (*a)[sizeof(*a) - 1] = '\0'; } +static void yearlist_to_string(const struct year_listlink *const y, + char (*const a)[12]) { year_to_string((const struct year *)y, a); } +#define TABLE_NAME year +#define TABLE_KEY struct year * +#define TABLE_UINT unsigned +#define TABLE_HASH &hash_year +#define TABLE_IS_EQUAL &year_is_equal +#define TABLE_EXPECT_TRAIT +#include "../src/table.h" +#define TABLE_DEFAULT 0 +#define TABLE_EXPECT_TRAIT +#include "../src/table.h" +#define TABLE_TO_STRING &year_to_string +#include "../src/table.h" -#include /* chdir (POSIX, not ANSI) */ -#include /* mode_t (umask) */ -#include /* umask */ +#include /* chdir (POSIX) */ +#include /* mode_t (POSIX) */ +#include /* umask (POSIX) */ +#include /* opendir readdir closedir */ +#include int main(int argc, char **argv) { int success = EXIT_FAILURE; + DIR *year_dir; + struct dirent *year_de; + struct year_pool year_pool = POOL_IDLE; + struct year_list order; + struct year_table years = TABLE_IDLE; + + /* Get the years list. fixme: this is way overkill; `int_array`. */ + errno = 0; + year_list_clear(&order); if(argc != 2) { fprintf(stderr, "Needs journal location.\n" "(should contain //.txt)\n"); goto finally; } - if(chdir(argv[1]) == -1) goto catch; /* Go to journal. */ + if(chdir(argv[1]) == -1 || !(year_dir = opendir("."))) goto catch; + while((year_de = readdir(year_dir))) { + struct stat st; + char *const start = year_de->d_name, *end; + long year = strtol(start, &end, 0); /* Need better. */ + struct year *y; + if(errno || *end != '\0' || (size_t)(end - start) >= year_string_size + || year < INT_MIN || year > INT_MAX) + { /*fprintf(stderr, "%s: doesn't look like a year.\n", + year_de->d_name);*/ errno = 0; continue; } + if(stat(year_de->d_name, &st)) goto catch; + if(!S_ISDIR(st.st_mode)) { /*fprintf(stderr, + "%s: isn't a directory.\n", year_de->d_name);*/ continue; } + if(!(y = year_pool_new(&year_pool))) goto catch; + y->year = (int)year; + strcpy(y->as_string, year_de->d_name); + year_list_push(&order, &y->link); + } + year_list_sort(&order); + fprintf(stderr, "Years: %s\n", year_list_to_string(&order)); + /* Go though each of the years, in order, and extract meta-information. */ { success = EXIT_SUCCESS; goto finally; } catch: perror("interpret"); finally: + year_table_(&years); + year_pool_(&year_pool); return EXIT_FAILURE; } diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..7884db1 --- /dev/null +++ b/src/list.h @@ -0,0 +1,434 @@ +/** @license 2017 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @abstract Source ; examples . + + @subtitle Doubly-linked component + + ![Example of a stochastic skip-list.](../web/list.png) + + In parlance of , list> is a circular + header, or sentinel, to a doubly-linked list of listlink>. This is a + closed structure, such that with with a pointer to any element, it is possible + to extract the entire list. + + @param[LIST_NAME] + `` that satisfies `C` naming conventions when mangled; required. `` is + private, whose names are prefixed in a manner to avoid collisions. + + @param[LIST_EXPECT_TRAIT] + Do not un-define certain variables for subsequent inclusion in a trait. + + @param[LIST_COMPARE_NAME, LIST_COMPARE, LIST_IS_EQUAL] + Compare trait contained in . An optional mangled name for + uniqueness and a function implementing either compare_fn> or + bipredicate_fn>. + + @param[LIST_TO_STRING_NAME, LIST_TO_STRING] + To string trait contained in . An optional mangled name for + uniqueness and function implementing to_string_fn>. + + @std C89 */ + +#ifndef LIST_NAME +#error Name LIST_NAME undefined. +#endif +#if defined(LIST_TO_STRING_NAME) || defined(LIST_TO_STRING) /* */ +#if defined(LIST_COMPARE_NAME) || defined(LIST_COMPARE) \ + || defined(LIST_IS_EQUAL) /* */ +#define LIST_TRAITS LIST_TO_STRING_TRAIT + LIST_COMPARE_TRAIT +#if LIST_TRAITS > 1 +#error Only one trait per include is allowed; use LIST_EXPECT_TRAIT. +#endif +#if LIST_TRAITS && !defined(LIST_BASE) +#error Trying to define a trait without defining the base datatype. +#endif +#if defined(LIST_TO_STRING_NAME) && !defined(LIST_TO_STRING) +#error LIST_TO_STRING_NAME requires LIST_TO_STRING. +#endif +#if defined(LIST_COMPARE_NAME) \ + && (!(!defined(LIST_COMPARE) ^ !defined(LIST_IS_EQUAL))) +#error LIST_COMPARE_NAME requires LIST_COMPARE or LIST_IS_EQUAL not both. +#endif + +#ifndef LIST_H /* */ + + +#if LIST_TRAITS == 0 /* */ + +/* */ + +static void PL_(unused_base_coda)(void); +static void PL_(unused_base)(void) { + L_(list_head)(0); L_(list_tail)(0); L_(list_previous)(0); L_(list_next)(0); + L_(list_clear)(0); L_(list_add_before)(0, 0); L_(list_add_after)(0, 0); + L_(list_unshift)(0, 0); L_(list_push)(0, 0); L_(list_remove)(0); + L_(list_shift)(0); L_(list_pop)(0); L_(list_to)(0, 0); + L_(list_to_before)(0, 0); L_(list_to_if)(0, 0, 0); L_(list_for_each)(0, 0); + L_(list_any)(0, 0); L_(list_self_correct)(0); + PL_(begin)(0, 0); PL_(next)(0); PL_(unused_base_coda)(); +} +static void PL_(unused_base_coda)(void) { PL_(unused_base)(); } + + +#elif defined(LIST_TO_STRING) /* base code --> */ +#define TO_STRING LIST_TO_STRING +#include "to_string.h" /** \include */ +#ifdef LIST_TEST /* */ +#undef SZ_ +#undef LIST_TO_STRING +#ifdef LIST_TO_STRING_NAME +#undef LIST_TO_STRING_NAME +#endif + + +#else /* to string trait --> */ +#include "list_coda.h" /** \include */ +#ifdef LIST_TEST /* */ +#undef LC_ +#ifdef LIST_COMPARE_NAME +#undef LIST_COMPARE_NAME +#endif +#ifdef LIST_COMPARE +#undef LIST_COMPARE +#endif +#ifdef LIST_IS_EQUAL +#undef LIST_IS_EQUAL +#endif + + +#endif /* traits --> */ + + +#ifdef LIST_EXPECT_TRAIT /* */ +#endif /* !trait --> */ +#undef LIST_TO_STRING_TRAIT +#undef LIST_COMPARE_TRAIT +#undef LIST_TRAITS diff --git a/src/list_coda.h b/src/list_coda.h new file mode 100644 index 0000000..9ee744d --- /dev/null +++ b/src/list_coda.h @@ -0,0 +1,406 @@ +/* @license 2021 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @subtitle Recur trait + + Included by `list.h`. + + @param[LC_] + A one-argument macro producing a name that is responsible for the name of the + functions. The caller is responsible for undefining `LC_`. + + @param[COMPARE_NAME, COMPARE_IS_EQUAL, COMPARE] + Optional unique name that satisfies `C` naming conventions when mangled, + and a function implementing, for `COMPARE_IS_EQUAL`, + bipredicate_fn> that establishes an equivalence relation, or + for `COMPARE`, compare_fn> that establishes a total + order. There can be multiple comparable functions, but only one can omit + `COMPARE_NAME`. + + @std C89 */ + +#if !defined(LC_) || !(defined(LIST_IS_EQUAL) ^ defined(LIST_COMPARE)) \ + || !defined(L_) || !defined(PL_) +#error Unexpected preprocessor symbols. +#endif + +#ifndef LIST_CODA_H /* */ + +/** Returns a boolean given two read-only listlink>. */ +typedef int (*PLC_(bipredicate_fn))(const struct L_(listlink) *, + const struct L_(listlink) *); + +#ifdef LIST_COMPARE /* */ + +/** @return If `lista` piecewise equals `listb`, which both can be null. + @order \O(min(|`lista`|, |`listb`|)) @allow */ +static int LC_(is_equal)(const struct L_(list) *const lista, + const struct L_(list) *const listb) { + const struct L_(listlink) *a, *b; + if(!lista) return !listb; + if(!listb) return 0; + for(a = lista->u.flat.next, b = listb->u.flat.next; ; + a = a->next, b = b->next) { + if(!a->next) return !b->next; + if(!b->next) return 0; + if(!PLC_(is_equal)(a, b)) return 0; + } +} + +/** Moves all local-duplicates of `from` to the end of `to`. + + For example, if `from` is `(A, B, B, A)`, it would concatenate the second + `(B)` to `to` and leave `(A, B, A)` in `from`. If one sort> `from` + first, `(A, A, B, B)`, the global duplicates will be transferred, `(A, B)`. + @order \O(|`from`|) @allow */ +static void LC_(duplicates_to)(struct L_(list) *const from, + struct L_(list) *const to) { + struct L_(listlink) *a = from->u.flat.next, *b, *temp; + assert(from); + if(!(b = a->next)) return; + while(b->next) { + if(!PLC_(is_equal)(a, b)) { + a = b, b = b->next; + } else { + temp = b, b = b->next; + PL_(remove)(temp); + if(to) PL_(push)(to, temp); + } + } +} + +static void PLC_(unused_coda_coda)(void); +static void PLC_(unused_coda)(void) { +#ifdef LIST_COMPARE /* */ + LC_(is_equal)(0, 0); LC_(duplicates_to)(0, 0); + PLC_(unused_coda_coda)(); +} +static void PLC_(unused_coda_coda)(void) { PLC_(unused_coda)(); } + +#ifdef BOX_COMPARE +#undef BOX_COMPARE +#endif +#ifdef BOX_IS_EQUAL +#undef BOX_IS_EQUAL +#endif +#ifdef BOX_COMPARE_NAME +#undef BOX_COMPARE_NAME +#endif diff --git a/src/pool.h b/src/pool.h new file mode 100644 index 0000000..a9f7d31 --- /dev/null +++ b/src/pool.h @@ -0,0 +1,378 @@ +/** @license 2021 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @abstract Source , depends on , and ; + examples . + + @subtitle Stable pool + + ![Example of Pool](../web/pool.png) + + pool> is a memory pool that stores type>. Pointers to + valid items in the pool are stable, but not generally in any order. When + removal is ongoing and uniformly sampled while reaching a steady-state size, + it will eventually settle in one contiguous region. + + @param[POOL_NAME, POOL_TYPE] + `

` that satisfies `C` naming conventions when mangled and a valid tag type, + type>, associated therewith; required. `` is private, whose + names are prefixed in a manner to avoid collisions. + + @param[POOL_CHUNK_MIN_CAPACITY] + Default is 8; optional number in + `[2, (SIZE_MAX - sizeof pool_chunk) / sizeof type]` that the capacity can + not go below. + + @param[POOL_EXPECT_TRAIT] + Do not un-define certain variables for subsequent inclusion in a trait. + + @param[POOL_TO_STRING_NAME, POOL_TO_STRING] + To string trait contained in ; `` that satisfies `C` naming + conventions when mangled and function implementing to_string_fn>. + There can be multiple to string traits, but only one can omit + `POOL_TO_STRING_NAME`. This container is only partially iterable: the values + are only the first chunk, so this is not very useful except for debugging. + + @depend [array](https://github.com/neil-edelman/array) + @depend [heap](https://github.com/neil-edelman/heap) + @std C89 */ + +#if !defined(POOL_NAME) || !defined(POOL_TYPE) +#error Name POOL_NAME undefined or tag type POOL_TYPE undefined. +#endif +#if defined(POOL_TO_STRING_NAME) || defined(POOL_TO_STRING) +#define POOL_TO_STRING_TRAIT 1 +#else +#define POOL_TO_STRING_TRAIT 0 +#endif +#define POOL_TRAITS POOL_TO_STRING_TRAIT +#if POOL_TRAITS > 1 +#error Only one trait per include is allowed; use POOL_EXPECT_TRAIT. +#endif +#if defined(POOL_TO_STRING_NAME) && !defined(POOL_TO_STRING) +#error POOL_TO_STRING_NAME requires POOL_TO_STRING. +#endif + +#ifndef POOL_H /* */ + + +#if POOL_TRAITS == 0 /* */ +#if POOL_CHUNK_MIN_CAPACITY < 2 +#error Pool chunk capacity error. +#endif + +/** A valid tag type set by `POOL_TYPE`. */ +typedef POOL_TYPE PP_(type); + +/* Size and chunk, which goes into a sorted array. */ +struct PP_(slot) { size_t size; PP_(type) *chunk; }; +#define ARRAY_NAME PP_(slot) +#define ARRAY_TYPE struct PP_(slot) +#include "array.h" + +/** Consists of a map of several chunks of increasing size and a free-heap. + Zeroed data is a valid state. To instantiate to an idle state, see + pool>, `POOL_IDLE`, `{0}` (`C99`,) or being `static`. + + ![States.](../web/states.png) */ +struct P_(pool) { + struct PP_(slot_array) slots; + struct poolfree_heap free0; /* Free-list in chunk-zero. */ + size_t capacity0; /* Capacity of chunk-zero. */ +}; + +/** @return Index of sorted chunk that is higher than `x` in `slots`, but + treating zero as special. @order \O(\log `chunks`) */ +static size_t PP_(upper)(const struct PP_(slot_array) *const slots, + const PP_(type) *const x) { + const struct PP_(slot) *const base = slots->data; + size_t n, b0, b1; + assert(slots && x); + if(!(n = slots->size)) return 0; + assert(base); + if(!--n) return 1; + /* The last one is a special case: it doesn't have an upper bound. */ + for(b0 = 1, --n; n; n /= 2) { + b1 = b0 + n / 2; + if(x < base[b1].chunk) { continue; } + else if(base[b1 + 1].chunk <= x) { b0 = b1 + 1; n--; continue; } + else { return b1 + 1; } + } + return b0 + (x >= base[slots->size - 1].chunk); +} + +/** Which chunk is `x` in `pool`? + @order \O(\log `chunks`), \O(\log \log `size`)? */ +static size_t PP_(chunk_idx)(const struct P_(pool) *const pool, + const PP_(type) *const x) { + struct PP_(slot) *const base = pool->slots.data; + size_t up; + assert(pool && pool->slots.size && base && x); + /* One chunk, assume it's in that chunk; first chunk is `capacity0`. */ + if(pool->slots.size <= 1 + || (x >= base[0].chunk && x < base[0].chunk + pool->capacity0)) + return assert(x >= base[0].chunk && x < base[0].chunk + pool->capacity0), + 0; + up = PP_(upper)(&pool->slots, x); + return assert(up), up - 1; +} + +/** Makes sure there are space for `n` further items in `pool`. + @return Success. */ +static int PP_(buffer)(struct P_(pool) *const pool, const size_t n) { + const size_t min_size = POOL_CHUNK_MIN_CAPACITY, + max_size = (size_t)-1 / sizeof(PP_(type)); + struct PP_(slot) *base = pool->slots.data, *slot; + PP_(type) *chunk; + size_t c, insert; + int is_recycled = 0; + assert(pool && min_size <= max_size && pool->capacity0 <= max_size && + (!pool->slots.size && !pool->free0.a.size /* !chunks[0] -> !free0 */ + || pool->slots.size && base + && base[0].size <= pool->capacity0 + && (!pool->free0.a.size + || pool->free0.a.size < base[0].size + && pool->free0.a.data[0] < base[0].size))); + + /* Ensure space for new slot. */ + if(!n || pool->slots.size && n <= pool->capacity0 + - base[0].size + pool->free0.a.size) return 1; /* Already enough. */ + if(max_size < n) return errno = ERANGE, 1; /* Request unsatisfiable. */ + if(!PP_(slot_array_buffer)(&pool->slots, 1)) return 0; + base = pool->slots.data; /* It may have moved! */ + + /* Figure out the capacity of the next chunk. */ + c = pool->capacity0; + if(pool->slots.size && base[0].size) { /* ~Golden ratio. */ + size_t c1 = c + (c >> 1) + (c >> 3); + c = (c1 < c || c1 > max_size) ? max_size : c1; + } + if(c < min_size) c = min_size; + if(c < n) c = n; + + /* Allocate it. */ + if(pool->slots.size && !base[0].size) + is_recycled = 1, chunk = realloc(base[0].chunk, c * sizeof *chunk); + else chunk = malloc(c * sizeof *chunk); + if(!chunk) { if(!errno) errno = ERANGE; return 0; } + pool->capacity0 = c; /* We only need to store the capacity of chunk 0. */ + if(is_recycled) return base[0].size = 0, base[0].chunk = chunk, 1; + + /* Evict chunk 0. */ + if(!pool->slots.size) insert = 0; + else insert = PP_(upper)(&pool->slots, base[0].chunk); + assert(insert <= pool->slots.size); + slot = PP_(slot_array_insert)(&pool->slots, 1, insert); + assert(slot); /* Made space for it before. */ + slot->chunk = base[0].chunk, slot->size = base[0].size; + base[0].chunk = chunk, base[0].size = 0; + return 1; +} + +/** Either `data` in `pool` is in a secondary chunk, in which case it + decrements the size, or it's the zero-chunk, where it gets added to the + free-heap. + @return Success. It may fail due to a free-heap memory allocation error. + @order Amortized \O(\log \log `items`) @throws[realloc] */ +static int PP_(remove)(struct P_(pool) *const pool, + const PP_(type) *const data) { + size_t c = PP_(chunk_idx)(pool, data); + struct PP_(slot) *slot = pool->slots.data + c; + assert(pool && pool->slots.size && data); + if(!c) { /* It's in the zero-slot, we need to deal with the free-heap. */ + const size_t idx = (size_t)(data - slot->chunk); + assert(pool->capacity0 && slot->size <= pool->capacity0 + && idx < slot->size); + if(idx + 1 == slot->size) { + /* Keep shrinking going while item on the free-heap are exposed. */ + while(--slot->size && !poolfree_heap_is_empty(&pool->free0)) { + const size_t free = poolfree_heap_peek(&pool->free0); + if(free < slot->size - 1) break; + assert(free == slot->size - 1); + poolfree_heap_pop(&pool->free0); + } + } else if(!poolfree_heap_add(&pool->free0, idx)) return 0; + } else if(assert(slot->size), !--slot->size) { + PP_(type) *const chunk = slot->chunk; + PP_(slot_array_remove)(&pool->slots, pool->slots.data + c); + free(chunk); + } + return 1; +} + +/** Initializes `pool` to idle. @order \Theta(1) @allow */ +static void P_(pool)(struct P_(pool) *const pool) { assert(pool), + PP_(slot_array)(&pool->slots), poolfree_heap(&pool->free0), + pool->capacity0 = 0; } + +/** Destroys `pool` and returns it to idle. @order \O(\log `data`) @allow */ +static void P_(pool_)(struct P_(pool) *const pool) { + struct PP_(slot) *s, *s_end; + assert(pool); + for(s = pool->slots.data, s_end = s + pool->slots.size; s < s_end; s++) + assert(s->chunk), free(s->chunk); + PP_(slot_array_)(&pool->slots); + poolfree_heap_(&pool->free0); + P_(pool)(pool); +} + +/** Ensure capacity of at least `n` further items in `pool`. Pre-sizing is + better for contiguous blocks, but takes up that memory. + @return Success. @throws[ERANGE, malloc] @allow */ +static int P_(pool_buffer)(struct P_(pool) *const pool, const size_t n) { + return assert(pool), PP_(buffer)(pool, n); +} + +/** This pointer is constant until it gets pool_remove>. + @return A pointer to a new uninitialized element from `pool`. + @throws[ERANGE, malloc] @order amortised O(1) @allow */ +static PP_(type) *P_(pool_new)(struct P_(pool) *const pool) { + struct PP_(slot) *slot0; + assert(pool); + if(!PP_(buffer)(pool, 1)) return 0; + assert(pool->slots.size && (pool->free0.a.size || + pool->slots.data[0].size < pool->capacity0)); + if(!poolfree_heap_is_empty(&pool->free0)) { + /* Cheating: we prefer the minimum index from a max-heap, but it + doesn't really matter, so take the one off the array used for heap. */ + size_t *free; + free = heap_poolfree_node_array_pop(&pool->free0.a); + return assert(free), pool->slots.data[0].chunk + *free; + } + /* The free-heap is empty; guaranteed by buffer>. */ + slot0 = pool->slots.data + 0; + assert(slot0 && slot0->size < pool->capacity0); + return slot0->chunk + slot0->size++; +} + +/** Deletes `data` from `pool`. Do not remove data that is not in `pool`. + @return Success. @order \O(\log \log `items`) @allow */ +static int P_(pool_remove)(struct P_(pool) *const pool, + PP_(type) *const data) { return PP_(remove)(pool, data); } + +/** Removes all from `pool`, but keeps it's active state, only freeing the + smaller blocks. @order \O(\log `items`) @allow */ +static void P_(pool_clear)(struct P_(pool) *const pool) { + struct PP_(slot) *s, *s_end; + assert(pool); + if(!pool->slots.size) { assert(!pool->free0.a.size); return; } + for(s = pool->slots.data + 1, s_end = s - 1 + pool->slots.size; + s < s_end; s++) assert(s->chunk && s->size), free(s->chunk); + pool->slots.data[0].size = 0; + pool->slots.size = 1; + poolfree_heap_clear(&pool->free0); +} + +/* */ + +#ifdef POOL_TEST /* */ + +/* */ +#define TO_STRING POOL_TO_STRING +#include "to_string.h" /** \include */ +#ifdef POOL_TEST /* */ +#undef SZ_ +#undef POOL_TO_STRING +#ifdef POOL_TO_STRING_NAME +#undef POOL_TO_STRING_NAME +#endif + + +#endif /* traits --> */ + + +#ifdef POOL_EXPECT_TRAIT /* */ +#endif /* !trait --> */ +#undef POOL_TO_STRING_TRAIT +#undef POOL_TRAITS diff --git a/src/table.h b/src/table.h new file mode 100644 index 0000000..2b8469f --- /dev/null +++ b/src/table.h @@ -0,0 +1,988 @@ +/** @license 2019 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @abstract Source ; examples . + + @subtitle Hash table + + ![Example of table.](../web/table.png) + + table> implements a set or map of entry> as a hash table. + It must be supplied a hash_fn> and, is_equal_fn> or + inverse_hash_fn>. + + @param[TABLE_NAME, TABLE_KEY] + `` that satisfies `C` naming conventions when mangled and a valid + key> associated therewith; required. `` is private, whose + names are prefixed in a manner to avoid collisions. + + @param[TABLE_HASH, TABLE_IS_EQUAL, TABLE_INVERSE] + `TABLE_HASH`, and either `TABLE_IS_EQUAL` or `TABLE_INVERSE`, but not both, + are required. Function satisfying hash_fn>, and + is_equal_fn> or inverse_hash_fn>. + + @param[TABLE_VALUE] + An optional type that is the payload of the key, thus making this a map or + associative array. (If the key is part of an aggregate pointer, it will be + more efficient and robust to use a set with a type conversion instead of + storing related pointers in a map.) + + @param[TABLE_UINT] + This is uint>, the unsigned type of hash hash of the key given by + hash_fn>; defaults to `size_t`. + + @param[TABLE_EXPECT_TRAIT] + Do not un-define certain variables for subsequent inclusion in a trait. + + @param[TABLE_DEFAULT_NAME, TABLE_DEFAULT] + Default trait; a name that satisfies `C` naming conventions when mangled and a + value> used in tableget>. There can be multiple + defaults, but only one can omit `TABLE_DEFAULT_NAME`. + + @param[TABLE_TO_STRING_NAME, TABLE_TO_STRING] + To string trait contained in ; `` that satisfies `C` naming + conventions when mangled and function implementing + to_string_fn>. There can be multiple to string traits, but only + one can omit `TABLE_TO_STRING_NAME`. + + @std C89 */ + +#if !defined(TABLE_NAME) || !defined(TABLE_KEY) || !defined(TABLE_HASH) \ + || !(defined(TABLE_IS_EQUAL) ^ defined(TABLE_INVERSE)) +#error Name TABLE_NAME, tag type TABLE_KEY, functions TABLE_HASH, and, \ + TABLE_IS_EQUAL or TABLE_INVERSE (but not both) undefined. +#endif +#if defined(TABLE_DEFAULT_NAME) || defined(TABLE_DEFAULT) +#define TABLE_DEFAULT_TRAIT 1 +#else +#define TABLE_DEFAULT_TRAIT 0 +#endif +#if defined(TABLE_TO_STRING_NAME) || defined(TABLE_TO_STRING) +#define TABLE_TO_STRING_TRAIT 1 +#else +#define TABLE_TO_STRING_TRAIT 0 +#endif +#define TABLE_TRAITS TABLE_DEFAULT_TRAIT + TABLE_TO_STRING_TRAIT +#if TABLE_TRAITS > 1 +#error Only one trait per include is allowed; use TABLE_EXPECT_TRAIT. +#endif +#if defined(TABLE_DEFAULT_NAME) && !defined(TABLE_DEFAULT) +#error TABLE_DEFAULT_NAME requires TABLE_DEFAULT. +#endif +#if defined(TABLE_TO_STRING_NAME) && !defined(TABLE_TO_STRING) +#error TABLE_TO_STRING_NAME requires TABLE_TO_STRING. +#endif + +#ifndef TABLE_H /* */ + + +#if TABLE_TRAITS == 0 /* */ + +#ifdef TABLE_VALUE /* */ + +/** @return Key from `e`. */ +static PN_(key) PN_(entry_key)(PN_(entry) e) { +#ifdef TABLE_VALUE + return e.key; +#else + return e; +#endif +} + +/* Address is hash modulo size of table. Any occupied buckets at the head of + the linked structure are closed, that is, the address equals the index. These + form a linked table, possibly with other, open buckets that have the same + address in vacant buckets. */ +struct PN_(bucket) { + PN_(uint) next; /* Bucket index, including `TABLE_NULL` and `TABLE_END`. */ + PN_(uint) hash; +#ifndef TABLE_INVERSE + PN_(key) key; +#endif +#ifdef TABLE_VALUE + PN_(value) value; +#endif +}; + +/** Gets the key of an occupied `bucket`. */ +static PN_(key) PN_(bucket_key)(const struct PN_(bucket) *const bucket) { + assert(bucket && bucket->next != TABLE_NULL); +#ifdef TABLE_INVERSE + return PN_(inverse_hash)(bucket->hash); +#else + return bucket->key; +#endif +} + +/** Gets the value of an occupied `bucket`, which might be the same as the + key. */ +static PN_(value) PN_(bucket_value)(const struct PN_(bucket) *const bucket) { + assert(bucket && bucket->next != TABLE_NULL); +#ifdef TABLE_VALUE + return bucket->value; +#else + return PN_(bucket_key)(bucket); +#endif +} + +/** Fills `entry`, a public structure, with the information of `bucket`. */ +static void PN_(to_entry)(const struct PN_(bucket) *const bucket, + PN_(entry) *const entry) { + assert(bucket && entry); +#ifdef TABLE_VALUE /* entry { key key; value value; } */ + entry->key = PN_(bucket_key)(bucket); + memcpy(&entry->value, &bucket->value, sizeof bucket->value); +#else /* entry key */ + *entry = PN_(bucket_key)(bucket); +#endif +} + +/** Returns true if the `replace` replaces the `original`. */ +typedef int (*PN_(policy_fn))(PN_(key) original, PN_(key) replace); + +/** To initialize, see table>, `TABLE_IDLE`, `{0}` (`C99`,) or being + `static`. The fields should be treated as read-only; any modification is + liable to cause the table to go into an invalid state. + + ![States.](../web/states.png) */ +struct N_(table) { /* "Padding size," good. */ + struct PN_(bucket) *buckets; /* @ has zero/one key specified by `next`. */ + /* `size <= capacity`; size is not needed but convenient and allows + short-circuiting. Index of the top of the stack; however, we are really + lazy, so MSB store is the top a step ahead? Thereby, hysteresis. */ + PN_(uint) log_capacity, size, top; +}; + +/** The capacity of a non-idle `table` is always a power-of-two. */ +static PN_(uint) PN_(capacity)(const struct N_(table) *const table) + { return assert(table && table->buckets && table->log_capacity >= 3), + (PN_(uint))((PN_(uint))1 << table->log_capacity); } + +/** @return Indexes the first closed bucket in the set of buckets with the same + address from non-idle `table` given the `hash`. If the bucket is empty, it + will have `next = TABLE_NULL` or it's own to_bucket> not equal to the + index. */ +static PN_(uint) PN_(to_bucket)(const struct N_(table) *const table, + const PN_(uint) hash) { return hash & (PN_(capacity)(table) - 1); } + +/** @return Search for the previous link in the bucket to `b` in `table`, if it + exists, (by restarting and going though the list.) @order \O(`bucket size`) */ +static struct PN_(bucket) *PN_(prev)(const struct N_(table) *const table, + const PN_(uint) b) { + const struct PN_(bucket) *const bucket = table->buckets + b; + PN_(uint) to_next = TABLE_NULL, next; + assert(table && bucket->next != TABLE_NULL); + /* Note that this does not check for corrupted tables; would get assert. */ + for(next = PN_(to_bucket)(table, bucket->hash); + /* assert(next < capacity), */ next != b; + to_next = next, next = table->buckets[next].next); + return to_next != TABLE_NULL ? table->buckets + to_next : 0; +} + +/* */ + +/** `TABLE_INVERSE` is injective, so in that case, we only compare hashes. + @return `a` and `b`. */ +static int PN_(equal_buckets)(PN_(ckey) a, PN_(ckey) b) { +#ifdef TABLE_INVERSE + return (void)a, (void)b, 1; +#else + return PN_(equal)(a, b); +#endif +} + +/** `table` will be searched linearly for `key` which has `hash`. + @fixme Move to front like splay trees? */ +static struct PN_(bucket) *PN_(query)(struct N_(table) *const table, + PN_(ckey) key, const PN_(uint) hash) { + struct PN_(bucket) *bucket1; + PN_(uint) head, b0 = TABLE_NULL, b1, b2; + assert(table && table->buckets && table->log_capacity); + bucket1 = table->buckets + (head = b1 = PN_(to_bucket)(table, hash)); + /* Not the start of a bucket: empty or in the collision stack. */ + if((b2 = bucket1->next) == TABLE_NULL + || PN_(in_stack_range)(table, b1) + && b1 != PN_(to_bucket)(table, bucket1->hash)) return 0; + while(hash != bucket1->hash + || !PN_(equal_buckets)(key, PN_(bucket_key)(bucket1))) { + if(b2 == TABLE_END) return 0; + bucket1 = table->buckets + (b0 = b1, b1 = b2); + assert(b1 < PN_(capacity)(table) && PN_(in_stack_range)(table, b1) + && b1 != TABLE_NULL); + b2 = bucket1->next; + } +#ifdef TABLE_DONT_SPLAY /* */ +} + +/** Ensures that `table` has enough buckets to fill `n` more than the size. May + invalidate and re-arrange the order. + @return Success; otherwise, `errno` will be set. @throws[realloc] + @throws[ERANGE] Tried allocating more then can fit in half uint> + or `realloc` doesn't follow [POSIX + ](https://pubs.opengroup.org/onlinepubs/009695399/functions/realloc.html). */ +static int PN_(buffer)(struct N_(table) *const table, const PN_(uint) n) { + struct PN_(bucket) *buckets; + const PN_(uint) log_c0 = table->log_capacity, + c0 = log_c0 ? (PN_(uint))((PN_(uint))1 << log_c0) : 0; + PN_(uint) log_c1, c1, size1, i, wait, mask; + assert(table && table->size <= TABLE_HIGH + && (!table->buckets && !table->size && !log_c0 && !c0 + || table->buckets && table->size <= c0 && log_c0>=3)); + /* Can we satisfy `n` growth from the buffer? */ + if(TABLE_M1 - table->size < n || TABLE_HIGH < (size1 = table->size + n)) + return errno = ERANGE, 0; + if(table->buckets) log_c1 = log_c0, c1 = c0 ? c0 : 1; + else log_c1 = 3, c1 = 8; + while(c1 < size1) log_c1++, c1 <<= 1; + if(log_c0 == log_c1) return 1; + + /* Otherwise, need to allocate more. */ + if(!(buckets = realloc(table->buckets, sizeof *buckets * c1))) + { if(!errno) errno = ERANGE; return 0; } + table->top = (c1 - 1) | TABLE_HIGH; /* No stack. */ + table->buckets = buckets, table->log_capacity = log_c1; + + /* Initialize new values. Mask to identify the added bits. */ + { struct PN_(bucket) *e = buckets + c0, *const e_end = buckets + c1; + for( ; e < e_end; e++) e->next = TABLE_NULL; } + mask = (PN_(uint))((((PN_(uint))1 << log_c0) - 1) + ^ (((PN_(uint))1 << log_c1) - 1)); + + /* Rehash most closed buckets in the lower half. Create waiting + linked-stack by borrowing next. */ + wait = TABLE_END; + for(i = 0; i < c0; i++) { + struct PN_(bucket) *idx, *go; + PN_(uint) g, hash; + idx = table->buckets + i; + if(idx->next == TABLE_NULL) continue; + g = PN_(to_bucket)(table, hash = idx->hash); + /* It's a power-of-two size, so, like consistent hashing, `E[old/new]` + capacity that a closed bucket will remain where it is. */ + if(i == g) { idx->next = TABLE_END; continue; } + if((go = table->buckets + g)->next == TABLE_NULL) { + /* Priority is given to the first closed bucket; simpler later. */ + struct PN_(bucket) *head; + PN_(uint) h = g & ~mask; assert(h <= g); + if(h < g && i < h + && (head = table->buckets + h, assert(head->next != TABLE_NULL), + PN_(to_bucket)(table, head->hash) == g)) { + memcpy(go, head, sizeof *head); + go->next = TABLE_END, head->next = TABLE_NULL; + /* Fall-though -- the bucket still needs to be put on wait. */ + } else { + /* If the new bucket is available and this bucket is first. */ + memcpy(go, idx, sizeof *idx); + go->next = TABLE_END, idx->next = TABLE_NULL; + continue; + } + } + idx->next = wait, wait = i; /* Push for next sweep. */ + } + + /* Search waiting stack for buckets that moved concurrently. */ + { PN_(uint) prev = TABLE_END, w = wait; while(w != TABLE_END) { + struct PN_(bucket) *waiting = table->buckets + w; + PN_(uint) cl = PN_(to_bucket)(table, waiting->hash); + struct PN_(bucket) *const closed = table->buckets + cl; + assert(cl != w); + if(closed->next == TABLE_NULL) { + memcpy(closed, waiting, sizeof *waiting), closed->next = TABLE_END; + if(prev != TABLE_END) table->buckets[prev].next = waiting->next; + if(wait == w) wait = waiting->next; /* First, modify head. */ + w = waiting->next, waiting->next = TABLE_NULL; + } else { + assert(closed->next == TABLE_END); /* Not in the wait stack. */ + prev = w, w = waiting->next; + } + }} + + /* Rebuild the top stack at the high numbers from the waiting at low. */ + while(wait != TABLE_END) { + struct PN_(bucket) *const waiting = table->buckets + wait; + PN_(uint) h = PN_(to_bucket)(table, waiting->hash); + struct PN_(bucket) *const head = table->buckets + h; + struct PN_(bucket) *top; + assert(h != wait && head->next != TABLE_NULL); + PN_(grow_stack)(table), top = table->buckets + table->top; + memcpy(top, waiting, sizeof *waiting); + top->next = head->next, head->next = table->top; + wait = waiting->next, waiting->next = TABLE_NULL; /* Pop. */ + } + + return 1; +} + +/** Replace the `key` and `hash` of `bucket`. Don't touch next. */ +static void PN_(replace_key)(struct PN_(bucket) *const bucket, + const PN_(key) key, const PN_(uint) hash) { + (void)key; + bucket->hash = hash; +#ifndef TABLE_INVERSE + memcpy(&bucket->key, &key, sizeof key); +#endif +} + +/** Replace the entire `entry` and `hash` of `bucket`. Don't touch next. */ +static void PN_(replace_entry)(struct PN_(bucket) *const bucket, + const PN_(entry) entry, const PN_(uint) hash) { + PN_(replace_key)(bucket, PN_(entry_key)(entry), hash); +#ifdef TABLE_VALUE + memcpy(&bucket->value, &entry.value, sizeof(entry.value)); +#endif +} + +/** Evicts the spot where `hash` goes in `table`. This results in a space in + the table. */ +static struct PN_(bucket) *PN_(evict)(struct N_(table) *const table, + const PN_(uint) hash) { + PN_(uint) i; + struct PN_(bucket) *bucket; + if(!PN_(buffer)(table, 1)) return 0; /* Amortized. */ + bucket = table->buckets + (i = PN_(to_bucket)(table, hash)); /* Closed. */ + if(bucket->next != TABLE_NULL) { /* Occupied. */ + int in_stack = PN_(to_bucket)(table, bucket->hash) != i; + PN_(move_to_top)(table, i); + bucket->next = in_stack ? TABLE_END : table->top; + } else { /* Unoccupied. */ + bucket->next = TABLE_END; + } + table->size++; + return bucket; +} + +/** Put `entry` in `table`. For collisions, only if `policy` exists and returns + true do and displace it to `eject`, if non-null. + @return A . @throws[malloc] + @order Amortized \O(max bucket length); the key to another bucket may have to + be moved to the top; the table might be full and have to be resized. */ +static enum table_result PN_(put)(struct N_(table) *const table, + PN_(entry) entry, PN_(entry) *eject, const PN_(policy_fn) policy) { + struct PN_(bucket) *bucket; + const PN_(key) key = PN_(entry_key)(entry); + const PN_(uint) hash = PN_(hash)(key); + enum table_result result; + assert(table); + if(table->buckets && (bucket = PN_(query)(table, key, hash))) { + if(!policy || !policy(PN_(bucket_key)(bucket), key)) return TABLE_YIELD; + if(eject) PN_(to_entry)(bucket, eject); + result = TABLE_REPLACE; + } else { + if(!(bucket = PN_(evict)(table, hash))) return TABLE_ERROR; + result = TABLE_UNIQUE; + } + PN_(replace_entry)(bucket, entry, hash); + return result; +} + +#ifdef TABLE_VALUE /* */ + +/** Initialises `table` to idle. @order \Theta(1) @allow */ +static void N_(table)(struct N_(table) *const table) { + assert(table); + table->buckets = 0; + table->log_capacity = 0; table->size = 0; table->top = 0; +} + +/** Destroys `table` and returns it to idle. @allow */ +static void N_(table_)(struct N_(table) *const table) + { assert(table), free(table->buckets); N_(table)(table); } + +/** Reserve at least `n` more empty buckets in `table`. This may cause the + capacity to increase and invalidates any pointers to data in the table. + @return Success. + @throws[ERANGE] The request was unsatisfiable. @throws[realloc] @allow */ +static int N_(table_buffer)(struct N_(table) *const table, const PN_(uint) n) + { return assert(table), PN_(buffer)(table, n); } + +/** Clears and removes all buckets from `table`. The capacity and memory of the + `table` is preserved, but all previous values are un-associated. (The load + factor will be less until it reaches it's previous size.) + @order \Theta(`table.capacity`) @allow */ +static void N_(table_clear)(struct N_(table) *const table) { + struct PN_(bucket) *b, *b_end; + assert(table); + if(!table->buckets) { assert(!table->log_capacity); return; } + assert(table->log_capacity); + for(b = table->buckets, b_end = b + PN_(capacity)(table); b < b_end; b++) + b->next = TABLE_NULL; + table->size = 0; + table->top = (PN_(capacity)(table) - 1) & TABLE_HIGH; +} + +/** @return Whether `key` is in `table` (which can be null.) @allow */ +static int N_(table_is)(struct N_(table) *const table, const PN_(key) key) + { return table && table->buckets + ? !!PN_(query)(table, key, PN_(hash)(key)) : 0; } + +/** @param[result] If null, behaves like table_is>, otherwise, a + entry> which gets filled on true. + @return Whether `key` is in `table` (which can be null.) @allow */ +static int N_(table_query)(struct N_(table) *const table, const PN_(key) key, + PN_(entry) *const result) { + struct PN_(bucket) *bucket; + if(!table || !table->buckets + || !(bucket = PN_(query)(table, key, PN_(hash)(key)))) return 0; + if(result) PN_(to_entry)(bucket, result); + return 1; +} + +/** @return The value associated with `key` in `table`, (which can be null.) If + no such value exists, `default_value` is returned. + @order Average \O(1); worst \O(n). @allow */ +static PN_(value) N_(table_get_or)(struct N_(table) *const table, + const PN_(key) key, PN_(value) default_value) { + struct PN_(bucket) *bucket; + return table && table->buckets + && (bucket = PN_(query)(table, key, PN_(hash)(key))) + ? PN_(bucket_value)(bucket) : default_value; +} + +/** Puts `entry` in `table` only if absent. + @return One of: `TABLE_ERROR`, the table is not modified; `TABLE_YIELD`, not + modified if there is another entry with the same key; `TABLE_UNIQUE`, put an + entry in the table. + @throws[realloc, ERANGE] On `TABLE_ERROR`. + @order Average amortised \O(1); worst \O(n). @allow */ +static enum table_result N_(table_try)(struct N_(table) *const table, + PN_(entry) entry) { return PN_(put)(table, entry, 0, 0); } + +/** Callback in table_replace>. + @return `original` and `replace` ignored, true. + @implements policy_fn> */ +static int PN_(always_replace)(const PN_(key) original, + const PN_(key) replace) { return (void)original, (void)replace, 1; } + +/** Puts `entry` in `table`. + @return One of: `TABLE_ERROR`, the table is not modified; `TABLE_REPLACE`, the + `entry` is put if the table, and, if non-null, `eject` will be filled; + `TABLE_UNIQUE`, on a unique entry. + @throws[realloc, ERANGE] On `TABLE_ERROR`. + @order Average amortised \O(1); worst \O(n). @allow */ +static enum table_result N_(table_replace)(struct N_(table) *const table, + PN_(entry) entry, PN_(entry) *eject) { + return PN_(put)(table, entry, eject, &PN_(always_replace)); +} + +/** Puts `entry` in `table` only if absent or if calling `policy` returns true. + @return One of: `TABLE_ERROR`, the table is not modified; `TABLE_REPLACE`, if + `update` is non-null and returns true, if non-null, `eject` will be filled; + `TABLE_YIELD`, if `update` is null or false; `TABLE_UNIQUE`, on unique entry. + @throws[realloc, ERANGE] On `TABLE_ERROR`. + @order Average amortised \O(1); worst \O(n). @allow */ +static enum table_result N_(table_update)(struct N_(table) *const table, + PN_(entry) entry, PN_(entry) *eject, const PN_(policy_fn) policy) + { return PN_(put)(table, entry, eject, policy); } + +#ifdef TABLE_VALUE /* */ + +/** Removes `key` from `table` (which could be null.) + @return Whether that `key` was in `table`. @order Average \O(1), (hash + distributes elements uniformly); worst \O(n). @allow */ +static int N_(table_remove)(struct N_(table) *const table, + const PN_(key) key) { + struct PN_(bucket) *current; + PN_(uint) crnt, prv = TABLE_NULL, nxt, hash = PN_(hash)(key); + if(!table || !table->size) return 0; assert(table->buckets); + /* Find item and keep track of previous. */ + current = table->buckets + (crnt = PN_(to_bucket)(table, hash)); + if((nxt = current->next) == TABLE_NULL + || PN_(in_stack_range)(table, crnt) + && crnt != PN_(to_bucket)(table, current->hash)) return 0; + while(hash != current->hash + && !PN_(equal_buckets)(key, PN_(bucket_key)(current))) { + if(nxt == TABLE_END) return 0; + prv = crnt, current = table->buckets + (crnt = nxt); + assert(crnt < PN_(capacity)(table) && PN_(in_stack_range)(table, crnt) + && crnt != TABLE_NULL); + nxt = current->next; + } + if(prv != TABLE_NULL) { /* Open entry. */ + struct PN_(bucket) *previous = table->buckets + prv; + previous->next = current->next; + } else if(current->next != TABLE_END) { /* Head closed entry and others. */ + struct PN_(bucket) *const second + = table->buckets + (crnt = current->next); + assert(current->next < PN_(capacity)(table)); + memcpy(current, second, sizeof *second); + current = second; + } + current->next = TABLE_NULL, table->size--, PN_(shrink_stack)(table, crnt); + return 1; +} + +/* */ + +/** ![States](../web/it.png) + + Adding, deleting, successfully looking up entries, or any modification of the + table's topology invalidates the iterator. + Iteration usually not in any particular order. The asymptotic runtime of + iterating though the whole table is proportional to the capacity. */ +struct N_(table_iterator); +struct N_(table_iterator) { struct PN_(iterator) it; + struct N_(table) *modify; union { PN_(uint) prev; void *do_not_warn; } _; }; + +/** Loads `table` (can be null) into `it`. @allow */ +static void N_(table_begin)(struct N_(table_iterator) *const it, + struct N_(table) *const table) { + PN_(begin)(&it->it, table); + /* Stupid: I want to have table_iterator_remove>; so I need a + non-constant value. The value in to string is constant. */ + it->modify = table; + it->_.prev = TABLE_NULL; +} + +/** Advances `prev` to keep up with `b` in `it`. + (Stupid: changing back to offset.) */ +static void PN_(advance)(struct N_(table_iterator) *const it, + const struct PN_(bucket) *const b) + { it->_.prev = (PN_(uint))(b - it->it.table->buckets); } + +/** Advances `it`. + @param[entry] If non-null, the entry is filled with the next element only if + it has a next. @return Whether it had a next element. @allow */ +static int N_(table_next)(struct N_(table_iterator) *const it, + PN_(entry) *entry) { + const struct PN_(bucket) *b = PN_(next)(&it->it); + return b ? (PN_(advance)(it, b), PN_(to_entry)(b, entry), 1) : 0; +} + +/** Especially for tables that can have zero as a valid value, this is used to + differentiate between zero and null. + @return Whether the table specified to `it` in table_begin> has a + next element. @order Amortized on the capacity, \O(1). @allow */ +static int N_(table_has_next)(struct N_(table_iterator) *const it) { + assert(it); + return it->it.table && it->it.table->buckets && PN_(skip)(&it->it); +} + +#ifdef TABLE_VALUE /* */ + +/** Removes the entry at `it`. Whereas table_remove> invalidates the + iterator, this corrects for a signal `it`. + @return Success, or there was no entry at the iterator's position, (anymore.) + @allow */ +static int N_(table_iterator_remove)(struct N_(table_iterator) *const it) { + struct N_(table) *table; + PN_(uint) b = it->_.prev; + struct PN_(bucket) *previous = 0, *current; + PN_(uint) prv = TABLE_NULL, crnt; + assert(it); + if(b == TABLE_NULL) return 0; + table = it->modify; + assert(table && table == it->it.table + && table->buckets && b < PN_(capacity)(table)); + /* Egregious code reuse. :[ */ + current = table->buckets + b, assert(current->next != TABLE_NULL); + crnt = PN_(to_bucket)(table, current->hash); + while(crnt != b) assert(crnt < PN_(capacity)(table)), + crnt = (previous = table->buckets + (prv = crnt))->next; + if(prv != TABLE_NULL) { /* Open entry. */ + previous->next = current->next; + } else if(current->next != TABLE_END) { /* Head closed entry and others. */ + const PN_(uint) scnd = current->next; + struct PN_(bucket) *const second = table->buckets + scnd; + assert(scnd < PN_(capacity)(table)); + memcpy(current, second, sizeof *second); + if(crnt < scnd) it->it._.b = it->_.prev; /* Iterate new entry. */ + crnt = scnd; current = second; + } + current->next = TABLE_NULL, table->size--, PN_(shrink_stack)(table, crnt); + it->_.prev = TABLE_NULL; + return 1; +} + +/* */ + +static void PN_(unused_base_coda)(void); +static void PN_(unused_base)(void) { + PN_(entry) e; PN_(key) k; PN_(value) v; + memset(&e, 0, sizeof e); memset(&k, 0, sizeof k); memset(&v, 0, sizeof v); + N_(table)(0); N_(table_)(0); N_(table_buffer)(0, 0); N_(table_clear)(0); + N_(table_is)(0, k); N_(table_query)(0, k, 0); N_(table_get_or)(0, k, v); + N_(table_try)(0, e); N_(table_replace)(0, e, 0); N_(table_update)(0,e,0,0); + N_(table_remove)(0, 0); N_(table_begin)(0, 0); N_(table_next)(0, 0); + N_(table_has_next)(0); N_(table_iterator_remove)(0);PN_(unused_base_coda)(); +#ifdef TABLE_VALUE + N_(table_compute)(0, k, 0); N_(table_next_key)(0); N_(table_next_value)(0); +#endif +} +static void PN_(unused_base_coda)(void) { PN_(unused_base)(); } + + +#elif defined(TABLE_DEFAULT) /* base --> */ +#undef TSZ_ +#undef SZ_ +#undef TABLE_TO_STRING +#ifdef TABLE_TO_STRING_NAME +#undef TABLE_TO_STRING_NAME +#endif + + +#endif /* traits --> */ + + +#ifdef TABLE_EXPECT_TRAIT /* */ +#endif /* !trait --> */ +#undef TABLE_DEFAULT_TRAIT +#undef TABLE_TO_STRING_TRAIT +#undef TABLE_TRAITS diff --git a/src/to_string.h b/src/to_string.h new file mode 100644 index 0000000..0bc249e --- /dev/null +++ b/src/to_string.h @@ -0,0 +1,156 @@ +/* @license 2020 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @subtitle To string trait + + A trait relying on the iterate interface (`iterator`, `begin`, `next`.) + + @param[SZ_] + A one-argument macro producing a name that is responsible for the name of the + to string function. Should be something like + `SZ_(to_string) -> widget_foo_to_string`. The caller is responsible for + undefining `SZ_`. + + @param[TO_STRING] + Function implementing to_string_fn>. + + @param[TO_STRING_LEFT, TO_STRING_RIGHT] + 7-bit characters, defaults to '(' and ')'. + + @param[TO_STRING_EXTERN, TO_STRING_INTERN] + Normally the space to put the temporary strings is static, one per file. With + this, it's possible to have a global storage to save space: have one file have + `TO_STRING_INTERN` as the first box, the other files `TO_STRING_EXTERN`. This + is unsynchronized. + + @fixme `extern` untested. + + @std C89 */ + +#if !defined(BOX_) || !defined(BOX_CONTAINER) || !defined(BOX_CONTENTS) \ + || !defined(SZ_) || !defined(TO_STRING) +#error Unexpected preprocessor symbols. Check that one including it as a trait. +#endif + +#if defined(TO_STRING_H) \ + && (defined(TO_STRING_EXTERN) || defined(TO_STRING_INTERN)) /* */ +#endif /* !not --> */ + +#ifndef TO_STRING_H /* */ +#else /* ntern --> */ +#endif /* idempotent --> */ + +#ifndef TO_STRING_LEFT +#define TO_STRING_LEFT '(' +#endif +#ifndef TO_STRING_RIGHT +#define TO_STRING_RIGHT ')' +#endif + +/* An alias to the box. */ +typedef BOX_CONTAINER PSZ_(box); + +/* An alias to the individual type contained in the box. */ +typedef BOX_CONTENTS PSZ_(type); + +/** : responsible for turning the argument into a 12-`char` + null-terminated output string. `type` is contracted to be an internal + iteration type of the box. */ +typedef void (*PSZ_(to_string_fn))(const PSZ_(type) *, char (*)[12]); +/* Check that `TO_STRING` is a function implementing + to_string>. */ +static const PSZ_(to_string_fn) PSZ_(to_string) = (TO_STRING); + +/** : print the contents of `box` in a static string buffer of + 256 bytes, with limitations of only printing 4 things at a time. `box` is + contracted to be the box itself. `` is loosely contracted to be a name + `box[]`. + @return Address of the static buffer. @order \Theta(1) @allow */ +static const char *SZ_(to_string)(const PSZ_(box) *const box) { + const char comma = ',', space = ' ', *const ellipsis = "…", + left = TO_STRING_LEFT, right = TO_STRING_RIGHT; + const size_t ellipsis_len = strlen(ellipsis); + char *const buffer = to_string_buffers[to_string_buffer_i++], *b = buffer; + size_t advance, size; + const PSZ_(type) *x; + struct BOX_(iterator) it; + int is_sep = 0; + /* Minimum size: "(" "XXXXXXXXXXX" "," "…" ")" "\0". */ + assert(box && !(to_string_buffers_no & (to_string_buffers_no - 1)) + && to_string_buffer_size >= 1 + 11 + 1 + ellipsis_len + 1 + 1); + /* Advance the buffer for next time. */ + to_string_buffer_i &= to_string_buffers_no - 1; + /* Begin iteration. */ + BOX_(begin)(&it, box); + *b++ = left; + while(x = BOX_(next)(&it)) { + PSZ_(to_string)(x, (char (*)[12])b); + /* Paranoid about '\0'. */ + for(advance = 0; *b != '\0' && advance < 11; b++, advance++); + is_sep = 1, *b++ = comma, *b++ = space; + /* Greedy typesetting: enough for "XXXXXXXXXXX" "," "…" ")" "\0". */ + if((size = (size_t)(b - buffer)) + > to_string_buffer_size - 11 - 1 - ellipsis_len - 1 - 1) + { if(BOX_(next)(&it)) goto ellipsis; else break; } + } + if(is_sep) b -= 2; + *b++ = right; + goto terminate; +ellipsis: + b--; + memcpy(b, ellipsis, ellipsis_len), b += ellipsis_len; + *b++ = right; +terminate: + *b++ = '\0'; + assert(b - buffer <= to_string_buffer_size); + return buffer; +} + +static void PSZ_(unused_to_string_coda)(void); +static void PSZ_(unused_to_string)(void) + { SZ_(to_string)(0); PSZ_(unused_to_string_coda)(); } +static void PSZ_(unused_to_string_coda)(void) { PSZ_(unused_to_string)(); } + +#undef TO_STRING +#ifdef TO_STRING_NAME +#undef TO_STRING_NAME +#endif +#ifdef TO_STRING_EXTERN +#undef TO_STRING_EXTERN +#endif +#ifdef TO_STRING_INTERN +#undef TO_STRING_INTERN +#endif +#undef TO_STRING_LEFT +#undef TO_STRING_RIGHT