Way overcomplicated.

This commit is contained in:
Neil 2022-02-11 00:28:28 -08:00
parent 58b228e9d0
commit b87906bb49
9 changed files with 3713 additions and 20 deletions

396
src/array.h Normal file
View File

@ -0,0 +1,396 @@
/** @license 2016 Neil Edelman, distributed under the terms of the
[MIT License](https://opensource.org/licenses/MIT).
@abstract Source <src/array.h>; examples <test/test_array.c>.
@subtitle Contiguous dynamic array
![Example of array.](../web/array.png)
<tag:<A>array> is a dynamic array that stores contiguous <typedef:<PA>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]
`<A>` that satisfies `C` naming conventions when mangled and a valid tag-type,
<typedef:<PA>type>, associated therewith; required. `<PA>` is private, whose
names are prefixed in a manner to avoid collisions.
@param[ARRAY_CODA]
Include more functions contained in <src/array_coda.h>, where `<AC>` is
`<A>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 <src/array_coda.h>. An optional mangled name for
uniqueness and a function implementing either <typedef:<PAC>compare_fn> or
<typedef:<PAC>bipredicate_fn>.
@param[ARRAY_TO_STRING_NAME, ARRAY_TO_STRING]
To string trait contained in <src/to_string.h>. An optional mangled name for
uniqueness and function implementing <typedef:<PSZ>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 /* <!-- idempotent */
#define ARRAY_H
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#if defined(ARRAY_CAT_) || defined(ARRAY_CAT) || defined(A_) || defined(PA_) \
|| defined(ARRAY_IDLE)
#error Unexpected defines.
#endif
/* <Kernighan and Ritchie, 1988, p. 231>. */
#define ARRAY_CAT_(n, m) n ## _ ## m
#define ARRAY_CAT(n, m) ARRAY_CAT_(n, m)
#define A_(n) ARRAY_CAT(ARRAY_NAME, n)
#define PA_(n) ARRAY_CAT(array, A_(n))
#define ARRAY_IDLE { 0, 0, 0 }
#endif /* idempotent --> */
#if ARRAY_TRAITS == 0 /* <!-- base code */
#ifndef ARRAY_MIN_CAPACITY /* <!-- !min; */
#define ARRAY_MIN_CAPACITY 3 /* > 1 */
#endif /* !min --> */
/** 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 <fn:<A>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
<fn:<A>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 <fn:<A>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;
}
/* <!-- iterate interface */
/* Contains all iteration parameters. */
struct PA_(iterator) { const struct A_(array) *a; size_t i; };
/** Loads `a` into `it`. @implements begin */
static void PA_(begin)(struct PA_(iterator) *const it,
const struct A_(array) *const a) { assert(it && a), it->a = a, it->i = 0; }
/** Advances `it`. @implements next */
static PA_(type) *PA_(next)(struct PA_(iterator) *const it) {
return assert(it && it->a), it->i < it->a->size ? it->a->data + it->i++ : 0;
}
#define BOX_ PA_
#define BOX_CONTAINER struct A_(array)
#define BOX_CONTENTS PA_(type)
/* iterate --> */
/* <!-- coda interface */
/** @return `a`. */
static const struct A_(array) *PA_(id_c)(const struct A_(array) *const a)
{ return a; }
/** @return `a`. */
static struct A_(array) *PA_(id)(struct A_(array) *const a) { return a; }
#define ARRAY_CODA_TYPE struct A_(array) /* Also box. */
#define ARRAY_CODA_BOX_TO_C &PA_(id_c)
#define ARRAY_CODA_BOX_TO &PA_(id)
#define AC_(n) ARRAY_CAT(A_(array), n)
/* coda --> */
#ifdef ARRAY_CODA /* <!-- coda: More functions. */
#include "array_coda.h" /** \include */
#endif /* coda --> */
#ifdef ARRAY_TEST /* <!-- test */
/* Forward-declare. */
static void (*PA_(to_string))(const PA_(type) *, char (*)[12]);
static const char *(*PA_(array_to_string))(const struct A_(array) *);
#include "../test/test_array.h" /* (this will needlessly confuse) \include */
#endif /* 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 --><!-- to string trait */
#ifdef ARRAY_TO_STRING_NAME
#define SZ_(n) ARRAY_CAT(A_(array), ARRAY_CAT(ARRAY_TO_STRING_NAME, n))
#else
#define SZ_(n) ARRAY_CAT(A_(array), n)
#endif
#define TO_STRING ARRAY_TO_STRING
#include "to_string.h" /** \include */
#ifdef ARRAY_TEST /* <!-- expect: greedy satisfy forward-declared. */
#undef ARRAY_TEST
static PSZ_(to_string_fn) PA_(to_string) = PSZ_(to_string);
static const char *(*PA_(array_to_string))(const struct A_(array) *)
= &SZ_(to_string);
#endif /* expect --> */
#undef SZ_
#undef ARRAY_TO_STRING
#ifdef ARRAY_TO_STRING_NAME
#undef ARRAY_TO_STRING_NAME
#endif
#else /* to string trait --><!-- compare trait */
#ifdef ARRAY_COMPARE_NAME
#define ARRAY_CODA_NAME ARRAY_COMPARE_NAME
#endif
#ifdef ARRAY_COMPARE /* <!-- cmp */
#define BOX_COMPARE ARRAY_COMPARE
#else /* cmp --><!-- eq */
#define BOX_IS_EQUAL ARRAY_IS_EQUAL
#endif /* eq --> */
#include "array_coda.h" /* (Already included.) */
#ifdef ARRAY_TEST /* <!-- test: this detects and outputs compare test. */
#include "../test/test_array.h"
#endif /* 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 /* <!-- trait */
#undef ARRAY_EXPECT_TRAIT
#else /* trait --><!-- !trait */
#ifdef ARRAY_TEST
#error No ARRAY_TO_STRING traits defined for ARRAY_TEST.
#endif
#undef ARRAY_NAME
#undef ARRAY_TYPE
/* Iteration. */
#undef BOX_
#undef BOX_CONTAINER
#undef BOX_CONTENTS
/* Coda. */
#undef ARRAY_CODA_TYPE
#undef ARRAY_CODA_BOX_TO_C
#undef ARRAY_CODA_BOX_TO
#undef AC_
#undef ARRAY_CODA_ONCE
#ifdef ARRAY_CODA_COMPARE_ONCE
#undef ARRAY_CODA_COMPARE_ONCE
#endif
#endif /* !trait --> */
#undef ARRAY_TO_STRING_TRAIT
#undef ARRAY_COMPARE_TRAIT
#undef ARRAY_TRAITS

437
src/array_coda.h Normal file
View File

@ -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 <typedef:<PAC>box_to_array_c> and
<typedef:<PAC>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 /* <!-- idempotent */
#define ARRAY_CODA_H
#include <limits.h>
#ifdef PAC_
#error Unexpected defines.
#endif
#define PAC_(n) ARRAY_CAT(array_coda, AC_(n))
#endif /* idempotent --> */
#ifndef ARRAY_CODA_ONCE /* <!-- once */
#define ARRAY_CODA_ONCE
/** <src/array_coda.h>: an alias to the box. */
typedef BOX_CONTAINER PAC_(box);
/** <src/array_coda.h>: an alias to the individual type contained in the box. */
typedef BOX_CONTENTS PAC_(type);
/* Downcasting. */
typedef ARRAY_CODA_TYPE PAC_(array);
typedef const PAC_(array) *(*PAC_(box_to_array_c))(const PAC_(box) *);
static PAC_(box_to_array_c) PAC_(b2a_c) = (ARRAY_CODA_BOX_TO_C);
typedef PAC_(array) *(*PAC_(box_to_array))(PAC_(box) *);
static PAC_(box_to_array) PAC_(b2a) = (ARRAY_CODA_BOX_TO);
#endif /* once --> */
#if !defined(BOX_IS_EQUAL) && !defined(BOX_COMPARE) /* <!-- functions */
/** <src/array_coda.h>: Operates by side-effects on <typedef:<PAC>type>. */
typedef void (*PAC_(action_fn))(PAC_(type) *);
/** <src/array_coda.h>: Returns a boolean given read-only <typedef:<PAC>type>. */
typedef int (*PAC_(predicate_fn))(const PAC_(type) *);
/** <src/array_coda.h> @param[x] A valid entry or null to start from the last.
@return The previous valid entry from `box` (which could be null) or null if
this was the first. @allow */
static PAC_(type) *AC_(previous)(const PAC_(box) *const box,
const PAC_(type) *const x) {
const PAC_(array) *a;
size_t i;
if(!box || !(a = PAC_(b2a_c)(box))->data) return 0;
if(!x) return a->size ? a->data + a->size - 1 : 0;
return (i = (size_t)(x - a->data)) ? a->data + i - 1 : 0;
}
/** <src/array_coda.h> @param[x] A valid entry or null to start from the first.
@return The next valid entry from `box` (which could be null) or null if this
was the last. @allow */
static PAC_(type) *AC_(next)(const PAC_(box) *const box,
const PAC_(type) *const x) {
const PAC_(array) *a;
size_t i;
if(!box || !(a = PAC_(b2a_c)(box))->data) return 0;
if(!x) return a->size ? a->data + 0 : 0;
return (i = (size_t)(x - a->data) + 1) < a->size ? a->data + i : 0;
}
/** <src/array_coda.h> @return Converts `i` to an index in `box` from
[0, `box.size`]. Negative values are wrapped. @order \Theta(1) @allow */
static size_t AC_(clip)(const PAC_(box) *const box, const long i) {
const PAC_(array) *const a = PAC_(b2a_c)(box);
/* `SIZE_MAX` is `C99`. This is not guaranteed at all, but is common? */
assert(box && a && ~((size_t)0) >= (size_t)LONG_MAX
&& (unsigned long)~((size_t)0) >= LONG_MAX);
return i < 0
? (size_t)-i >= a->size ? 0 : a->size - (size_t)-i
: (size_t)i > a->size ? a->size : (size_t)i;
}
/** <src/array_coda.h>: For all elements of `b`, calls `copy`, and if true, lazily
copies the elements to `a`. `a` and `b` can not be the same but `b` can be
null, (in which case, it does nothing.)
@order \O(`b.size` \times `copy`) @throws[ERANGE, realloc] @allow */
static int AC_(copy_if)(PAC_(box) *const a, const PAC_(predicate_fn) copy,
const PAC_(box) *const b) {
PAC_(array) *const aa = PAC_(b2a)(a);
const PAC_(array) *const bb = b ? PAC_(b2a_c)(b) : 0;
PAC_(type) *i, *fresh;
const PAC_(type) *end, *rise = 0;
size_t add;
int difcpy = 0;
assert(a && aa && !(!b ^ !bb) && copy && a != b && aa != bb);
if(!b) return 1;
for(i = bb->data, end = i + bb->size; i < end; i++) {
if(!(!!rise ^ (difcpy = copy(i)))) continue; /* Not falling/rising. */
if(difcpy) { /* Rising edge. */
assert(!rise);
rise = i;
} else { /* Falling edge. */
assert(rise && !difcpy && rise < i);
if(!(fresh = BOX_(append)(a, add = (size_t)(i - rise)))) return 0;
memcpy(fresh, rise, sizeof *fresh * add);
rise = 0;
}
}
if(rise) { /* Delayed copy. */
assert(!difcpy && rise < i);
if(!(fresh = BOX_(append)(a, add = (size_t)(i - rise)))) return 0;
memcpy(fresh, rise, sizeof *fresh * add);
}
return 1;
}
/** <src/array_coda.h>: For all elements of `box`, calls `keep`, and if false, lazy
deletes that item, calling `destruct` (if not-null).
@order \O(`a.size` \times `keep` \times `destruct`) @allow */
static void AC_(keep_if)(PAC_(box) *const box,
const PAC_(predicate_fn) keep, const PAC_(action_fn) destruct) {
PAC_(array) *const a = PAC_(b2a)(box);
PAC_(type) *erase = 0, *t;
const PAC_(type) *retain = 0, *end;
int keep0 = 1, keep1 = 0;
assert(box && a && keep);
for(t = a->data, end = t + a->size; t < end; keep0 = keep1, t++) {
if(!(keep1 = !!keep(t)) && destruct) destruct(t);
if(!(keep0 ^ keep1)) continue; /* Not a falling/rising edge. */
if(keep1) { /* Rising edge. */
assert(erase && !retain);
retain = t;
} else if(erase) { /* Falling edge. */
size_t n = (size_t)(t - retain);
assert(erase < retain && retain < t);
memmove(erase, retain, sizeof *t * n);
erase += n;
retain = 0;
} else { /* Falling edge, (first time only.) */
erase = t;
}
}
if(!erase) return; /* All elements were kept. */
if(keep1) { /* Delayed move when the iteration ended; repeat. */
size_t n = (size_t)(t - retain);
assert(retain && erase < retain && retain < t);
memmove(erase, retain, sizeof *t * n);
erase += n;
}
/* Adjust the size. */
assert((size_t)(erase - a->data) <= a->size);
a->size = (size_t)(erase - a->data);
}
/** <src/array_coda.h>: Removes at either end of `box` of things that `predicate`
returns true. @order \O(`box.size` \times `predicate`) @allow */
static void AC_(trim)(PAC_(box) *const box,
const PAC_(predicate_fn) predicate) {
PAC_(array) *const a = PAC_(b2a)(box);
size_t i;
assert(box && a && predicate);
while(a->size && predicate(a->data + a->size - 1)) a->size--;
for(i = 0; i < a->size && predicate(a->data + i); i++);
if(!i) return;
assert(i < a->size);
memmove(a->data, a->data + i, sizeof *a->data * i), a->size -= i;
}
/** <src/array_coda.h>: Iterates through `box` and calls `action` on all the
elements. The topology of the list should not change while in this function.
@order \O(`box.size` \times `action`) @allow */
static void AC_(each)(PAC_(box) *const box, const PAC_(action_fn) action) {
PAC_(array) *const a = PAC_(b2a)(box);
PAC_(type) *i, *end;
assert(box && a && action);
for(i = a->data, end = i + a->size; i < end; i++) action(i);
}
/** <src/array_coda.h>: Iterates through `box` and calls `action` on all the
elements for which `predicate` returns true. The topology of the list should
not change while in this function.
@order \O(`box.size` \times `predicate` \times `action`) @allow */
static void AC_(if_each)(PAC_(box) *const box,
const PAC_(predicate_fn) predicate, const PAC_(action_fn) action) {
PAC_(array) *const a = PAC_(b2a)(box);
PAC_(type) *i, *end;
assert(box && a && predicate && action);
for(i = a->data, end = i + a->size; i < end; i++)
if(predicate(i)) action(i);
}
/** <src/array_coda.h>: Iterates through `box` and calls `predicate` until it
returns true.
@return The first `predicate` that returned true, or, if the statement is
false on all, null. @order \O(`box.size` \times `predicate`) @allow */
static const PAC_(type) *AC_(any)(const PAC_(box) *const box,
const PAC_(predicate_fn) predicate) {
const PAC_(array) *const a = PAC_(b2a_c)(box);
PAC_(type) *i, *end;
assert(box && a && predicate);
for(i = a->data, end = i + a->size; i < end; i++)
if(predicate(i)) return i;
return 0;
}
static void PAC_(unused_function_coda)(void);
static void PAC_(unused_function)(void)
{ AC_(previous)(0, 0); AC_(next)(0, 0); AC_(clip)(0, 0);
AC_(copy_if)(0, 0, 0); AC_(keep_if)(0, 0, 0); AC_(trim)(0, 0);
AC_(each)(0, 0); AC_(if_each)(0, 0, 0); AC_(any)(0, 0);
PAC_(unused_function_coda)(); }
static void PAC_(unused_function_coda)(void) { PAC_(unused_function)(); }
#else /* functions --><!-- compare/is equal */
#ifndef ARRAY_CODA_COMPARE_ONCE /* <!-- once */
#define ARRAY_CODA_COMPARE_ONCE
/** <src/array_coda.h>: Returns a boolean given two read-only <typedef:<PAC>type>. */
typedef int (*PAC_(bipredicate_fn))(const PAC_(type) *, const PAC_(type) *);
/** <src/array_coda.h>: Three-way comparison on a totally order set of
<typedef:<PAC>type>; returns an integer value less then, equal to, greater
then zero, if `a < b`, `a == b`, `a > b`, respectively. */
typedef int (*PAC_(compare_fn))(const PAC_(type) *a, const PAC_(type) *b);
/** <src/array_coda.h>: Returns a boolean given two <typedef:<PAC>type>. */
typedef int (*PAC_(biaction_fn))(PAC_(type) *, PAC_(type) *);
#endif /* once --> */
#ifdef ARRAY_CODA_NAME
#define ACC_(n) AC_(ARRAY_CAT(ARRAY_CODA_NAME, n))
#else /* name --><!-- !name */
#define ACC_(n) AC_(n)
#endif /* !name --> */
#define PACC_(n) ARRAY_CAT(array_coda, ACC_(n))
#ifdef BOX_COMPARE /* <!-- compare */
/* Check that `BOX_COMPARE` is a function implementing
<typedef:<PAC>compare_fn>. */
static const PAC_(compare_fn) PACC_(compare) = (BOX_COMPARE);
/** <src/array_coda.h>: Lexicographically compares `a` to `b`. Both can be null,
with null values before everything. @return `a < b`: negative; `a == b`: zero;
`a > b`: positive. @order \O(`a.size`) @allow */
static int ACC_(compare)(const PAC_(box) *const a, const PAC_(box) *const b) {
const PAC_(array) *aa, *bb;
PAC_(type) *ad, *bd, *end;
int diff;
/* Null counts as `-\infty`. */
if(!a) return b ? -1 : 0;
else if(!b) return 1;
aa = PAC_(b2a_c)(a), bb = PAC_(b2a_c)(b), assert(aa && bb);
if(aa->size > bb->size) {
for(ad = aa->data, bd = bb->data, end = bd + bb->size; bd < end;
ad++, bd++) if((diff = PACC_(compare)(ad, bd))) return diff;
return 1;
} else {
for(ad = a->data, bd = b->data, end = ad + a->size; ad < end;
ad++, bd++) if((diff = PACC_(compare)(ad, bd))) return diff;
return -(aa->size != bb->size);
}
}
/** <src/array_coda.h>: `box` should be partitioned true/false with less-then
`value`. @return The first index of `a` that is not less than `value`.
@order \O(log `a.size`) @allow */
static size_t ACC_(lower_bound)(const PAC_(box) *const box,
const PAC_(type) *const value) {
const PAC_(array) *a = PAC_(b2a_c)(box);
size_t low = 0, high = a->size, mid;
assert(box && a && value);
while(low < high)
if(PACC_(compare)(value, a->data + (mid = low + (high - low) / 2)) <= 0)
high = mid;
else
low = mid + 1;
return low;
}
/** <src/array_coda.h>: `box` should be partitioned false/true with greater-than or
equal-to <typedef:<PAC>type> `value`. @return The first index of `box` that is
greater than `value`. @order \O(log `a.size`) @allow */
static size_t ACC_(upper_bound)(const PAC_(box) *const box,
const PAC_(type) *const value) {
const PAC_(array) *a = PAC_(b2a_c)(box);
size_t low = 0, high = a->size, mid;
assert(box && a && value);
while(low < high) if(PACC_(compare)(value, a->data
+ (mid = low + ((high - low) >> 1))) >= 0) low = mid + 1;
else high = mid;
return low;
}
/** <src/array_coda.h>: Copies `value` at the upper bound of a sorted `box`.
@return Success. @order \O(`a.size`) @throws[realloc, ERANGE] @allow */
static int ACC_(insert_after)(PAC_(box) *const box,
const PAC_(type) *const value) {
PAC_(array) *a = PAC_(b2a)(box);
size_t bound;
assert(box && a && value);
bound = ACC_(upper_bound)(a, value);
if(!A_(array_new)(a)) return 0; /* @fixme Reference to array. */
memmove(a->data + bound + 1, a->data + bound,
sizeof *a->data * (a->size - bound - 1));
memcpy(a->data + bound, value, sizeof *value);
return 1;
}
/** Wrapper with void `a` and `b`. @implements qsort bsearch */
static int PACC_(vcompar)(const void *const a, const void *const b)
{ return PACC_(compare)(a, b); }
/** <src/array_coda.h>: Sorts `box` by `qsort`.
@order \O(`a.size` \log `box.size`) @allow */
static void ACC_(sort)(PAC_(box) *const box) {
const PAC_(array) *a = PAC_(b2a_c)(box);
assert(box && a);
qsort(a->data, a->size, sizeof *a->data, &PACC_(vcompar));
}
/** Wrapper with void `a` and `b`. @implements qsort bsearch */
static int PACC_(vrevers)(const void *const a, const void *const b)
{ return PACC_(compare)(b, a); }
/** <src/array_coda.h>: Sorts `box` in reverse by `qsort`.
@order \O(`a.size` \log `a.size`) @allow */
static void ACC_(reverse)(PAC_(box) *const box) {
const PAC_(array) *a = PAC_(b2a_c)(box);
assert(box && a);
qsort(a->data, a->size, sizeof *a->data, &PACC_(vrevers));
}
/** !compare(`a`, `b`) == equals(`a`, `b`).
@implements <typedef:<PAC>bipredicate_fn> */
static int PACC_(is_equal)(const PAC_(type) *const a, const PAC_(type) *const b)
{ return !PACC_(compare)(a, b); }
#else /* compare --><!-- is equal */
/* Check that `BOX_IS_EQUAL` is a function implementing
<typedef:<PAC>bipredicate_fn>. */
static const PAC_(bipredicate_fn) PACC_(is_equal) = (BOX_IS_EQUAL);
#endif /* is equal --> */
/** <src/array_coda.h> @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;
}
/** <src/array_coda.h>: 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;
}
/** <src/array_coda.h>: 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 /* <!-- compare */
ACC_(compare)(0, 0); ACC_(lower_bound)(0, 0); ACC_(upper_bound)(0, 0);
ACC_(insert_after)(0, 0); ACC_(sort)(0); ACC_(reverse)(0);
#endif /* 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 --> */

432
src/heap.h Normal file
View File

@ -0,0 +1,432 @@
/** @license 2020 Neil Edelman, distributed under the terms of the
[MIT License](https://opensource.org/licenses/MIT).
@abstract Source <src/heap.h>, depends on <src/array.h>; examples
<test/test_heap.c>.
@subtitle Priority-queue
![Example of heap.](../web/heap.png)
A <tag:<H>heap> is a binary heap, proposed by
<Williams, 1964, Heapsort, p. 347> using terminology of
<Knuth, 1973, Sorting>. It can be used as an implementation of a priority
queue; internally, it is a `<<PH>node>array` with implicit heap properties on
<typedef:<PH>priority> and an optional <typedef:<PH>value> pointer value.
@param[HEAP_NAME, HEAP_TYPE]
`<H>` that satisfies `C` naming conventions when mangled and an assignable
type <typedef:<PH>priority> associated therewith. `HEAP_NAME` is required;
`HEAP_TYPE` defaults to `unsigned int`. `<PH>` is private, whose names are
prefixed in a manner to avoid collisions.
@param[HEAP_COMPARE]
A function satisfying <typedef:<PH>compare_fn>. Defaults to minimum-hash.
Required if `HEAP_TYPE` is changed to an incomparable type.
@param[HEAP_VALUE]
Optional value <typedef:<PH>value>, that is stored as a reference in
<tag:<H>heapnode>; declaring it is sufficient. If set, has no effect on the
ranking, but affects <typedef:<PH>value>, (otherwise, it's the same field as
<typedef:<PH>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 <to_string.h>; an optional unique `<SZ>`
that satisfies `C` naming conventions when mangled and function implementing
<typedef:<PSZ>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) /* <!-- str */
#define HEAP_TO_STRING_TRAIT 1
#else /* str --><!-- !str */
#define HEAP_TO_STRING_TRAIT 0
#endif /* !str --> */
#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 /* <!-- idempotent */
#define HEAP_H
#if defined(HEAP_CAT_) || defined(HEAP_CAT) || defined(H_) || defined(PH_) \
|| defined(HEAP_IDLE)
#error Unexpected defines.
#endif
/* <Kernighan and Ritchie, 1988, p. 231>. */
#define HEAP_CAT_(n, m) n ## _ ## m
#define HEAP_CAT(n, m) HEAP_CAT_(n, m)
#define H_(n) HEAP_CAT(HEAP_NAME, n)
#define PH_(n) HEAP_CAT(heap, H_(n))
#define HEAP_IDLE { ARRAY_IDLE }
#endif /* idempotent --> */
#if HEAP_TRAITS == 0 /* <!-- base code */
#ifndef HEAP_TYPE
#define HEAP_TYPE unsigned
#endif
/** Valid assignable type used for priority in <typedef:<PH>node>. Defaults to
`unsigned int` if not set by `HEAP_TYPE`. */
typedef HEAP_TYPE PH_(priority);
/** Returns a positive result if `a` is out-of-order with respect to `b`,
inducing a strict pre-order. This is compatible, but less strict then the
comparators from `bsearch` and `qsort`; it only needs to divide entries into
two instead of three categories. */
typedef int (*PH_(compare_fn))(const PH_(priority) a, const PH_(priority) b);
#ifndef HEAP_COMPARE /* <!-- !cmp */
/** The default `HEAP_COMPARE` on `a` and `b` is `a > b`, which makes a
minimum-hash. @implements <typedef:<PH>compare_fn> */
static int PH_(default_compare)(const PH_(priority) a, const PH_(priority) b)
{ return a > b; }
#define HEAP_COMPARE &PH_(default_compare)
#endif /* !cmp --> */
/* Check that `HEAP_COMPARE` is a function implementing
<typedef:<PH>compare_fn>, if defined. */
static const PH_(compare_fn) PH_(compare) = (HEAP_COMPARE);
#ifdef HEAP_VALUE /* <!-- value */
typedef HEAP_VALUE PH_(value_data);
typedef PH_(value_data) *PH_(value);
/** If `HEAP_VALUE` is set, this becomes <typedef:<PH>node>; make a temporary
structure to add a pointer to the value and a priority (which may be something
cached from the value) and copy it using <fn:<H>heap_add>. */
struct H_(heapnode) { PH_(priority) priority; PH_(value) value; };
/** If `HEAP_VALUE` is set, (priority, value) set by <tag:<H>heapnode>,
otherwise it's a (priority) set directly by <typedef:<PH>priority>. */
typedef struct H_(heapnode) PH_(node);
#else /* value --><!-- !value */
typedef PH_(priority) PH_(value);
typedef PH_(priority) PH_(node);
#endif /* !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 <fn:<H>heap>, `HEAP_IDLE`, `{0}` (`C99`),
or being `static`.
![States.](../web/states.png) */
struct H_(heap) { struct PH_(node_array) a; };
/** Extracts the <typedef:<PH>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 <typedef:<PH>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 --><!-- !value */
return *node;
#endif /* !value --> */
}
/** Copies `src` to `dest`. */
static void PH_(copy)(const PH_(node) *const src, PH_(node) *const dest) {
#ifdef HEAP_VALUE /* <!-- value */
dest->priority = src->priority;
dest->value = src->value;
#else /* value --><!-- !value */
*dest = *src;
#endif /* !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 <fn:<PH>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
<fn:<H>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 <fn:<H>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 <fn:<H>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 <Floyd, 1964, Treesort> to
sift-down all the internal nodes of heap. The heap elements must exist, see
<fn:<H>heap_buffer>.
@param[n] If zero, returns true without heapifying.
@return Success. @order \O(`heap.size` + `n`) <Doberkat, 1984, Floyd> @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;
}
/* <!-- iterate interface: Forward the responsibility to array. */
#define PAH_(n) HEAP_CAT(array, HEAP_CAT(PH_(node), n))
struct PH_(iterator) { struct PAH_(iterator) a; };
/** Begins the forward iteration `it` at `h`. */
static void PH_(begin)(struct PH_(iterator) *const it,
const struct H_(heap) *const h) { PAH_(begin)(&it->a, &h->a); }
/** @return The next `it` or null. */
static PH_(node) *PH_(next)(struct PH_(iterator) *const it)
{ return PAH_(next)(&it->a); }
#undef PAH_
/* iterate --> */
/* Define these for traits. */
#define BOX_ PH_
#define BOX_CONTAINER struct H_(heap)
#define BOX_CONTENTS PH_(node)
#ifdef HEAP_TEST /* <!-- test */
/* Forward-declare. */
static void (*PH_(to_string))(const PH_(node) *, char (*const)[12]);
static const char *(*PH_(heap_to_string))(const struct H_(heap) *);
#include "../test/test_heap.h"
#endif /* 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 --><!-- to string trait */
#ifdef HEAP_TO_STRING_NAME /* <!-- name */
#define SZ_(n) HEAP_CAT(H_(heap), HEAP_CAT(HEAP_TO_STRING_NAME, n))
#else /* name --><!-- !name */
#define SZ_(n) HEAP_CAT(H_(heap), n)
#endif /* !name --> */
#define TSZ_(n) HEAP_CAT(heap_sz, SZ_(n))
#ifdef HEAP_VALUE /* <!-- value */
/* Check that `HEAP_TO_STRING` is a function implementing this prototype. */
static void (*const TSZ_(actual_to_string))(const PH_(value_data) *,
char (*const)[12]) = (HEAP_TO_STRING);
/** Call <data:<TSZ>actual_to_string> with just the value of `node` and `z`. */
static void TSZ_(thunk_to_string)(const PH_(node) *const node,
char (*const z)[12]) { TSZ_(actual_to_string)(node->value, z); }
#define TO_STRING &TSZ_(thunk_to_string)
#else /* value --><!-- !value */
#define TO_STRING HEAP_TO_STRING
#endif /* !value --> */
#include "to_string.h" /** \include */
#ifdef HEAP_TEST /* <!-- expect: greedy satisfy forward-declared. */
#undef HEAP_TEST
static PSZ_(to_string_fn) PH_(to_string) = PSZ_(to_string);
static const char *(*PH_(heap_to_string))(const struct H_(heap) *)
= &SZ_(to_string);
#endif /* expect --> */
#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 /* <!-- trait */
#undef HEAP_EXPECT_TRAIT
#else /* trait --><!-- !trait */
#if defined(HEAP_TEST)
#error No HEAP_TO_STRING traits defined for HEAP_TEST.
#endif
#undef HEAP_NAME
#undef HEAP_TYPE
#undef HEAP_COMPARE
#ifdef HEAP_VALUE
#undef HEAP_VALUE
#endif
#undef BOX_
#undef BOX_CONTAINER
#undef BOX_CONTENTS
/* box (multiple traits) --> */
#endif /* !trait --> */
#undef HEAP_TO_STRING_TRAIT
#undef HEAP_TRAITS

View File

@ -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 <unistd.h> /* chdir (POSIX, not ANSI) */
#include <sys/types.h> /* mode_t (umask) */
#include <sys/stat.h> /* umask */
#include <unistd.h> /* chdir (POSIX) */
#include <sys/types.h> /* mode_t (POSIX) */
#include <sys/stat.h> /* umask (POSIX) */
#include <dirent.h> /* opendir readdir closedir */
#include <limits.h>
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 <year>/<month>/<day>.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;
}

434
src/list.h Normal file
View File

@ -0,0 +1,434 @@
/** @license 2017 Neil Edelman, distributed under the terms of the
[MIT License](https://opensource.org/licenses/MIT).
@abstract Source <src/list.h>; examples <test/test_list.c>.
@subtitle Doubly-linked component
![Example of a stochastic skip-list.](../web/list.png)
In parlance of <Thareja 2014, Structures>, <tag:<L>list> is a circular
header, or sentinel, to a doubly-linked list of <tag:<L>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]
`<L>` that satisfies `C` naming conventions when mangled; required. `<PL>` 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 <src/list_coda.h>. An optional mangled name for
uniqueness and a function implementing either <typedef:<PLC>compare_fn> or
<typedef:<PLC>bipredicate_fn>.
@param[LIST_TO_STRING_NAME, LIST_TO_STRING]
To string trait contained in <src/to_string.h>. An optional mangled name for
uniqueness and function implementing <typedef:<PSZ>to_string_fn>.
@std C89 */
#ifndef LIST_NAME
#error Name LIST_NAME undefined.
#endif
#if defined(LIST_TO_STRING_NAME) || defined(LIST_TO_STRING) /* <!-- str */
#define LIST_TO_STRING_TRAIT 1
#else /* str --><!-- !str */
#define LIST_TO_STRING_TRAIT 0
#endif /* !str --> */
#if defined(LIST_COMPARE_NAME) || defined(LIST_COMPARE) \
|| defined(LIST_IS_EQUAL) /* <!-- comp */
#define LIST_COMPARE_TRAIT 1
#else /* comp --><!-- !comp */
#define LIST_COMPARE_TRAIT 0
#endif /* !comp --> */
#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 /* <!-- idempotent */
#define LIST_H
#include <assert.h>
#if defined(LIST_CAT_) || defined(LIST_CAT) || defined(L_) || defined(PL_)
#error Unexpected defines.
#endif
/* <Kernighan and Ritchie, 1988, p. 231>. */
#define LIST_CAT_(n, m) n ## _ ## m
#define LIST_CAT(n, m) LIST_CAT_(n, m)
#define L_(n) LIST_CAT(LIST_NAME, n)
#define PL_(n) LIST_CAT(list, L_(n))
#endif /* idempotent --> */
#if LIST_TRAITS == 0 /* <!-- base code */
#define LIST_BASE
/** Storage of this structure is the responsibility of the caller, who must
provide a stable pointer while in a list. Generally, one encloses this in a
host `struct` or `union`. The contents of this structure should be treated as
read-only while in the list.
![States.](../web/node-states.png) */
struct L_(listlink);
struct L_(listlink) { struct L_(listlink) *next, *prev; };
/** Serves as head and tail for linked-list of <tag:<L>listlink>. Use
<fn:<L>list_clear> to initialize the list. Because this list is closed; that
is, given a valid pointer to an element, one can determine all others, null
values are not allowed and it is _not_ the same as `{0}`. The contents of this
structure should be treated as read-only while initialized, with the exception
of <fn:<L>list_self_correct>.
![States.](../web/states.png) */
struct L_(list);
struct L_(list) {
union {
struct { struct L_(listlink) head, *part_of_tail; } as_head;
struct { struct L_(listlink) *part_of_head, tail; } as_tail;
struct { struct L_(listlink) *next, *zero, *prev; } flat;
} u;
};
/** Operates by side-effects on the node. */
typedef void (*PL_(action_fn))(struct L_(listlink) *);
/** Returns (Non-zero) true or (zero) false when given a node. */
typedef int (*PL_(predicate_fn))(const struct L_(listlink) *);
/** Cats all `from` in front of `after`; `from` will be empty after.
Careful that `after` is not in `from` because that will just erase the list.
@order \Theta(1) */
static void PL_(move)(struct L_(list) *const from,
struct L_(listlink) *const after) {
assert(from && from->u.flat.next && !from->u.flat.zero && from->u.flat.prev
&& after && after->prev);
from->u.flat.next->prev = after->prev;
after->prev->next = from->u.as_head.head.next;
from->u.flat.prev->next = after;
after->prev = from->u.as_tail.tail.prev;
from->u.flat.next = &from->u.as_tail.tail;
from->u.flat.prev = &from->u.as_head.head;
}
/** @return A pointer to the first element of `list`, if it exists.
@order \Theta(1) @allow */
static struct L_(listlink) *L_(list_head)(const struct L_(list) *const list) {
struct L_(listlink) *link;
assert(list);
link = list->u.flat.next, assert(link);
return link->next ? link : 0;
}
/** @return A pointer to the last element of `list`, if it exists.
@order \Theta(1) @allow */
static struct L_(listlink) *L_(list_tail)(const struct L_(list) *const list) {
struct L_(listlink) *link;
assert(list);
link = list->u.flat.prev, assert(link);
return link->prev ? link : 0;
}
/** @return The previous element. When `link` is the first element, returns
null. @order \Theta(1) @allow */
static struct L_(listlink) *L_(list_previous)(struct L_(listlink) *link) {
assert(link && link->prev);
link = link->prev;
return link->prev ? link : 0;
}
/** @return The next element. When `link` is the last element, returns null.
@order \Theta(1) @allow */
static struct L_(listlink) *L_(list_next)(struct L_(listlink) *link) {
assert(link && link->next);
link = link->next;
return link->next ? link : 0;
}
/** Clear `list`. */
static void PL_(clear)(struct L_(list) *const list) {
list->u.flat.next = &list->u.as_tail.tail;
list->u.flat.zero = 0;
list->u.flat.prev = &list->u.as_head.head;
}
/** Clears and initializes `list`. @order \Theta(1) @allow */
static void L_(list_clear)(struct L_(list) *const list)
{ assert(list); PL_(clear)(list); }
/** `add` before `anchor`. @order \Theta(1) */
static void PL_(add_before)(struct L_(listlink) *const anchor,
struct L_(listlink) *const add) {
add->prev = anchor->prev;
add->next = anchor;
anchor->prev->next = add;
anchor->prev = add;
}
/** `add` before `anchor`. @order \Theta(1) @allow */
static void L_(list_add_before)(struct L_(listlink) *const anchor,
struct L_(listlink) *const add) {
assert(anchor && add && anchor != add && anchor->prev);
PL_(add_before)(anchor, add);
}
/** `add` after `anchor`. @order \Theta(1) */
static void PL_(add_after)(struct L_(listlink) *const anchor,
struct L_(listlink) *const add) {
add->prev = anchor;
add->next = anchor->next;
anchor->next->prev = add;
anchor->next = add;
}
/** `add` after `anchor`. @order \Theta(1) @allow */
static void L_(list_add_after)(struct L_(listlink) *const anchor,
struct L_(listlink) *const add) {
assert(anchor && add && anchor != add && anchor->next);
PL_(add_after)(anchor, add);
}
/** Adds `add` to the end of `list`. @order \Theta(1) */
static void PL_(push)(struct L_(list) *const list,
struct L_(listlink) *const add)
{ PL_(add_before)(&list->u.as_tail.tail, add); }
/** Adds `add` to the end of `list`. @order \Theta(1) @allow */
static void L_(list_push)(struct L_(list) *const list,
struct L_(listlink) *const add)
{ assert(list && add), PL_(push)(list, add); }
/** Adds `add` to the beginning of `list`. @order \Theta(1) @allow */
static void L_(list_unshift)(struct L_(list) *const list,
struct L_(listlink) *const add)
{ assert(list && add), PL_(add_after)(&list->u.as_head.head, add); }
/** Remove `node`. @order \Theta(1) */
static void PL_(remove)(struct L_(listlink) *const node) {
node->prev->next = node->next;
node->next->prev = node->prev;
node->prev = node->next = 0;
}
/** Remove `node`. @order \Theta(1) @allow */
static void L_(list_remove)(struct L_(listlink) *const node)
{ assert(node && node->prev && node->next), PL_(remove)(node); }
/** Removes the first element of `list` and returns it, if any.
@order \Theta(1) @allow */
static struct L_(listlink) *L_(list_shift)(struct L_(list) *const list) {
struct L_(listlink) *node;
assert(list && list->u.flat.next);
if(!(node = list->u.flat.next)->next) return 0;
L_(list_remove)(node);
return node;
}
/** Removes the last element of `list` and returns it, if any.
@order \Theta(1) @allow */
static struct L_(listlink) *L_(list_pop)(struct L_(list) *const list) {
struct L_(listlink) *node;
assert(list && list->u.flat.prev);
if(!(node = list->u.flat.prev)->prev) return 0;
L_(list_remove)(node);
return node;
}
/** Moves the elements `from` onto `to` at the end.
@param[to] If null, then it removes elements from `from`.
@order \Theta(1) @allow */
static void L_(list_to)(struct L_(list) *const from,
struct L_(list) *const to) {
assert(from && from != to);
if(!to) { PL_(clear)(from); return; }
PL_(move)(from, &to->u.as_tail.tail);
}
/** Moves the elements `from` immediately before `anchor`, which can not be in
the same list. @order \Theta(1) @allow */
static void L_(list_to_before)(struct L_(list) *const from,
struct L_(listlink) *const anchor) {
assert(from && anchor);
PL_(move)(from, anchor);
}
/** Moves all elements `from` onto `to` at the tail if `predicate` is true.
They ca'n't be the same list.
@param[to] If null, then it removes elements.
@order \Theta(|`from`|) \times \O(`predicate`) @allow */
static void L_(list_to_if)(struct L_(list) *const from,
struct L_(list) *const to, const PL_(predicate_fn) predicate) {
struct L_(listlink) *link, *next_link;
assert(from && from != to && predicate);
for(link = from->u.flat.next; next_link = link->next; link = next_link) {
if(!predicate(link)) continue;
L_(list_remove)(link);
if(to) L_(list_add_before)(&to->u.as_tail.tail, link);
}
}
/** Performs `action` for each element in `list` in order.
@param[action] It makes a double of the next node, so it can be to delete the
element and even assign it's values null.
@order \Theta(|`list`|) \times O(`action`) @allow */
static void L_(list_for_each)(struct L_(list) *const list,
const PL_(action_fn) action) {
struct L_(listlink) *x, *next_x;
assert(list && action);
for(x = list->u.flat.next; next_x = x->next; x = next_x) action(x);
}
/** Iterates through `list` and calls `predicate` until it returns true.
@return The first `predicate` that returned true, or, if the statement is
false on all, null.
@order \O(|`list`|) \times \O(`predicate`) @allow */
static struct L_(listlink) *L_(list_any)(const struct L_(list) *const list,
const PL_(predicate_fn) predicate) {
struct L_(listlink) *link, *next_link;
assert(list && predicate);
for(link = list->u.flat.next; next_link = link->next; link = next_link)
if(predicate(link)) return link;
return 0;
}
/** Corrects `list` ends to compensate for memory relocation of the list
itself. (Can only have one copy of the list, this will invalidate all other
copies.) @order \Theta(1) @allow */
static void L_(list_self_correct)(struct L_(list) *const list) {
assert(list && !list->u.flat.zero);
if(!list->u.flat.next->next) { /* Empty. */
assert(!list->u.flat.prev->prev);
list->u.flat.next = &list->u.as_tail.tail;
list->u.flat.prev = &list->u.as_head.head;
} else { /* Non-empty. */
list->u.flat.prev->next = &list->u.as_tail.tail;
list->u.flat.next->prev = &list->u.as_head.head;
}
}
/* <!-- iterate interface */
/* Contains all iteration parameters. (Since this is a permutation, the
iteration is defined by none other then itself. Used for traits.) */
struct PL_(iterator) { struct L_(listlink) *link; };
/** Loads `list` into `it`. @implements begin */
static void PL_(begin)(struct PL_(iterator) *const it,
const struct L_(list) *const list)
{ assert(it && list), it->link = L_(list_head)(list); }
/** Advances `it`. @implements next */
static const struct L_(listlink) *PL_(next)(struct PL_(iterator) *const it) {
struct L_(listlink) *here = it->link;
assert(it);
if(!here) return 0;
it->link = L_(list_next)(it->link);
return here;
}
/* iterate --> */
/* <!-- box (multiple traits) */
#define BOX_ PL_
#define BOX_CONTAINER struct L_(list)
#define BOX_CONTENTS struct L_(listlink)
#ifdef LIST_TEST /* <!-- test */
/* Forward-declare. */
static void (*PL_(to_string))(const struct L_(listlink) *, char (*)[12]);
static const char *(*PL_(list_to_string))(const struct L_(list) *);
#include "../test/test_list.h"
#endif /* test --> */
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 --><!-- to string trait */
#ifdef LIST_TO_STRING_NAME /* <!-- name */
#define SZ_(n) LIST_CAT(L_(list), LIST_CAT(LIST_TO_STRING_NAME, n))
#else /* name --><!-- !name */
#define SZ_(n) LIST_CAT(L_(list), n)
#endif /* !name --> */
#define TO_STRING LIST_TO_STRING
#include "to_string.h" /** \include */
#ifdef LIST_TEST /* <!-- expect: greedy satisfy forward-declared. */
#undef LIST_TEST
static PSZ_(to_string_fn) PL_(to_string) = PSZ_(to_string);
static const char *(*PL_(list_to_string))(const struct L_(list) *)
= &SZ_(to_string);
#endif /* expect --> */
#undef SZ_
#undef LIST_TO_STRING
#ifdef LIST_TO_STRING_NAME
#undef LIST_TO_STRING_NAME
#endif
#else /* to string trait --><!-- compare trait */
#ifdef LIST_COMPARE_NAME /* <!-- name */
#define LC_(n) LIST_CAT(L_(list), LIST_CAT(LIST_COMPARE_NAME, n))
#else /* name --><!-- !name */
#define LC_(n) LIST_CAT(L_(list), n)
#endif /* !name --> */
#include "list_coda.h" /** \include */
#ifdef LIST_TEST /* <!-- test: this detects and outputs compare test. */
#include "../test/test_list.h"
#endif /* 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 /* <!-- trait */
#undef LIST_EXPECT_TRAIT
#else /* trait --><!-- !trait */
#ifdef LIST_TEST
#error No LIST_TO_STRING traits defined for LIST_TEST.
#endif
#undef LIST_NAME
#undef BOX_
#undef BOX_CONTAINER
#undef BOX_CONTENTS
#undef LIST_BASE
/* box (multiple traits) --> */
#endif /* !trait --> */
#undef LIST_TO_STRING_TRAIT
#undef LIST_COMPARE_TRAIT
#undef LIST_TRAITS

406
src/list_coda.h Normal file
View File

@ -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`,
<typedef:<PLC>bipredicate_fn> that establishes an equivalence relation, or
for `COMPARE`, <typedef:<PLC>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 /* <!-- idempotent */
#define LIST_CODA_H
#if defined(PLC_)
#error Unexpected defines.
#endif
#define PLC_(n) LIST_CAT(list_coda, LC_(n))
/* <fn:<PLC>boolean> operations bit-vector; dummy ensures closed. */
enum list_operation {
LIST_SUBTRACTION_AB = 1,
LIST_SUBTRACTION_BA = 2, RECURA,
LIST_INTERSECTION = 4, RECURB, RECURC, RECURD,
LIST_DEFAULT_A = 8, RECURE, RECURF, RECURG, RECURH, RECURI, RECURJ,
RECURK,
LIST_DEFAULT_B = 16, RECURL, RECURM, RECURN, RECURO, RECURP, RECURQ,
RECURR, RECURS, RECURT, RECURU, RECURV, RECURW, RECURX, RECURY, RECURZ
};
#endif /* idempotent --> */
/** Returns a boolean given two read-only <tag:<L>listlink>. */
typedef int (*PLC_(bipredicate_fn))(const struct L_(listlink) *,
const struct L_(listlink) *);
#ifdef LIST_COMPARE /* <!-- compare */
/** Three-way comparison on a totally order set of <tag:<L>listlink>;
returns an integer value less then, equal to, greater then zero, if
`a < b`, `a == b`, `a > b`, respectively. */
typedef int (*PLC_(compare_fn))(const struct L_(listlink) *a,
const struct L_(listlink) *b);
/* Check that `LIST_COMPARE` is a function implementing
<typedef:<PLC>compare_fn>. */
static const PLC_(compare_fn) PLC_(compare) = (LIST_COMPARE);
/** Lexicographically compares `alist` to `blist`. Null values are before
everything.
@return `a < b`: negative; `a == b`: zero; `a > b`: positive.
@implements <typedef:<PLC>compare_fn> (one can `qsort` an array of lists, as
long as one calls <fn:<L>list_self_correct> on it's elements)
@order \Theta(min(|`alist`|, |`blist`|)) @allow */
static int LC_(compare)(const struct L_(list) *const alist,
const struct L_(list) *const blist) {
struct L_(listlink) *a, *b;
int diff;
/* Null counts as `-\infty`. */
if(!alist) {
return blist ? -1 : 0;
} else if(!blist) {
return 1;
}
/* Compare element by element. */
for(a = alist->u.flat.next, b = blist->u.flat.next; ;
a = a->next, b = b->next) {
if(!a->next) {
return b->next ? -1 : 0;
} else if(!b->next) {
return 1;
} else if((diff = PLC_(compare)(a, b))) {
return diff;
}
}
}
/** Merges `from` into `to`, preferring elements from `to` go in the front.
@order \O(|`from`| + |`to`|). */
static void LC_(merge)(struct L_(list) *const to, struct L_(list) *const from) {
struct L_(listlink) *head, **x = &head, *prev = &to->u.as_head.head, *t, *f;
assert(to && to->u.flat.next && to->u.flat.prev
&& from && from->u.flat.next && from->u.flat.prev && from != to);
/* Empty. */
if(!(f = from->u.flat.next)->next) return;
if(!(t = to->u.flat.next)->next)
{ PL_(move)(from, &to->u.as_tail.tail); return; }
/* Exclude sentinel. */
from->u.flat.prev->next = to->u.flat.prev->next = 0;
/* Merge. */
for( ; ; ) {
if(PLC_(compare)(t, f) <= 0) {
t->prev = prev, prev = *x = t, x = &t->next;
if(!(t = t->next)) { *x = f; goto from_left; }
} else {
f->prev = prev, prev = *x = f, x = &f->next;
if(!(f = f->next)) { *x = t; break; }
}
}
if(0) {
from_left:
f->prev = prev;
/* Switch sentinels. */
f = from->u.flat.prev;
to->u.flat.prev = f;
f->next = &from->u.as_tail.tail;
} else {
t->prev = prev;
}
/* Erase `from`. */
from->u.flat.next = &from->u.as_tail.tail;
from->u.flat.prev = &from->u.as_head.head;
}
/** Merges the two top runs referenced by `head_ptr` in stack form. */
static void PLC_(merge_runs)(struct L_(listlink) **const head_ptr) {
struct L_(listlink) *head = *head_ptr, **x = &head, *b = head, *a = b->prev,
*const prev = a->prev;
assert(head_ptr && a && b);
for( ; ; ) {
if(PLC_(compare)(a, b) <= 0) {
*x = a, x = &a->next;
if(!(a = a->next)) { *x = b; break; }
} else {
*x = b, x = &b->next;
if(!(b = b->next)) { *x = a; break; }
}
}
head->prev = prev, *head_ptr = head; /* `prev` is the previous run. */
}
/** The list form of `list` is restored from `head` in stack form with two
runs. */
static void PLC_(merge_final)(struct L_(list) *const list,
struct L_(listlink) *head) {
struct L_(listlink) *prev = 0, **x = &list->u.flat.next,
*b = head, *a = head->prev;
assert(list && b && a && !a->prev);
for( ; ; ) {
if(PLC_(compare)(a, b) <= 0) {
a->prev = prev, prev = *x = a, x = &a->next;
if(!(a = a->next)) { a = *x = b; break; }
} else {
b->prev = prev, prev = *x = b, x = &b->next;
if(!(b = b->next)) { *x = a; break; }
}
}
do; while(a->prev = prev, prev = a, a = a->next);
prev->next = &list->u.as_tail.tail, list->u.flat.prev = prev;
/* Not empty. */
assert(list->u.flat.next && list->u.flat.next != &list->u.as_tail.tail);
list->u.flat.next->prev = &list->u.as_head.head;
}
/** Natural merge sort `list`; the requirement for \O(\log |`list`|) space is
satisfied by converting it to a singly-linked with `prev` as a stack of
increasing lists, which are merged. */
static void PLC_(sort)(struct L_(list) *const list) {
/* Add `[-1,0,1]`: unique identifier for nine weakly-ordered transitions. */
enum { DEC = 1, EQ = 4, INC = 7 };
int mono = EQ, cmp;
struct L_(listlink) *a, *b, *c, *dec_iso = /* Unused. */0;
struct { size_t count; struct L_(listlink) *head, *prev; } run;
/* Closed sentinel list. */
assert(list
&& list->u.flat.next && !list->u.flat.zero && list->u.flat.prev);
if(a = list->u.flat.next, !(b = a->next)) return; /* Empty. */
/* Identify runs of monotonicity until `b` sentinel. */
run.count = 0, run.prev = 0, run.head = a;
for(c = b->next; c; a = b, b = c, c = c->next) {
cmp = PLC_(compare)(b, a);
switch(mono + (0 < cmp) - (cmp < 0)) {
/* Valley and mountain inflection. */
case INC - 1: a->next = 0; /* _Sic_. */
case DEC + 1: break;
/* Decreasing more and levelled off from decreasing. */
case DEC - 1: b->next = dec_iso; dec_iso = run.head = b; continue;
case DEC + 0: b->next = a->next; a->next = b; continue;
/* Turning down and up. */
case EQ - 1: a->next = 0; b->next = run.head; dec_iso = run.head = b;
mono = DEC; continue;
case EQ + 1: mono = INC; continue;
case EQ + 0: /* Same. _Sic_. */
case INC + 0: /* Levelled off from increasing. _Sic_. */
case INC + 1: continue; /* Increasing more. */
}
/* Binary carry sequence, <https://oeis.org/A007814>, one delayed so
always room for final merge. */
if(run.count) {
size_t rc;
for(rc = run.count - 1; rc & 1; rc >>= 1)
PLC_(merge_runs)(&run.prev);
}
/* Add to runs, advance; `b` becomes `a` forthwith. */
run.head->prev = run.prev, run.prev = run.head, run.count++;
run.head = b, mono = EQ;
}
/* Last run; go into an accepting state. */
if(mono != DEC) {
if(!run.count) return; /* Sorted already. */
a->next = 0; /* Last one of increasing or equal. */
} else { /* Decreasing. */
assert(dec_iso);
run.head = dec_iso;
if(!run.count) { /* Restore the pointers without having two runs. */
list->u.flat.next = dec_iso, dec_iso->prev = &list->u.as_head.head;
for(a = dec_iso, b = a->next; b; a = b, b = b->next) b->prev = a;
list->u.flat.prev = a, a->next = &list->u.as_tail.tail;
return; /* Reverse sorted; now good as well. */
}
}
assert(run.count);
/* (Actually slower to merge the last one eagerly. So do nothing.) */
run.head->prev = run.prev, run.count++;
/* Merge leftovers from the other direction, saving one for final. */
while(run.head->prev->prev) PLC_(merge_runs)(&run.head);
PLC_(merge_final)(list, run.head);
}
/** Performs a stable, adaptive sort of `list` according to `compare`.
@order \Omega(|`list`|), \O(|`list`| log |`list`|) @allow */
static void LC_(sort)(struct L_(list) *const list) { PLC_(sort)(list); }
/** Private: `alist` `mask` `blist` -> `result`. Prefers `a` to `b` when equal.
Either could be null.
@order \O(|`a`| + |`b`|) */
static void PLC_(boolean)(struct L_(list) *const alist,
struct L_(list) *const blist,
const enum list_operation mask, struct L_(list) *const result) {
struct L_(listlink) *temp,
*a = alist ? alist->u.flat.next : 0,
*b = blist ? blist->u.flat.next : 0;
int comp;
assert((!result || (result != alist && result != blist))
&& (!alist || (alist != blist)));
if(a && b) {
while(a->next && b->next) {
comp = PLC_(compare)(a, b);
if(comp < 0) {
temp = a, a = a->next;
if(mask & LIST_SUBTRACTION_AB) {
PL_(remove)(temp);
if(result) PL_(push)(result, temp);
}
} else if(comp > 0) {
temp = b, b = b->next;
if(mask & LIST_SUBTRACTION_BA) {
PL_(remove)(temp);
if(result) PL_(push)(result, temp);
}
} else {
temp = a, a = a->next, b = b->next;
if(mask & LIST_INTERSECTION) {
PL_(remove)(temp);
if(result) PL_(push)(result, temp);
}
}
}
}
if(a && mask & LIST_DEFAULT_A) {
while((temp = a, a = a->next)) {
PL_(remove)(temp);
if(result) PL_(push)(result, temp);
}
}
if(b && mask & LIST_DEFAULT_B) {
while((temp = b, b = b->next)) {
PL_(remove)(temp);
if(result) PL_(push)(result, temp);
}
}
}
/** Subtracts `a` from `b`, as sequential sorted individual elements, and moves
it to `result`. All elements are removed from `a`. All parameters must be
unique or can be null.
For example, if `a` contains `(A, B, D)` and `b` contains `(B, C)` then
`(a:A, a:D)` would be moved to `result`.
@order \O(|`a`| + |`b`|) @allow */
static void LC_(subtraction_to)(struct L_(list) *const a,
struct L_(list) *const b, struct L_(list) *const result) {
PLC_(boolean)(a, b, LIST_SUBTRACTION_AB | LIST_DEFAULT_A, result);
}
/** Moves the union of `a` and `b` as sequential sorted individual elements to
`result`. Equal elements are moved from `a`. All parameters must be unique or
can be null.
For example, if `a` contains `(A, B, D)` and `b` contains `(B, C)` then
`(a:A, a:B, b:C, a:D)` would be moved to `result`.
@order \O(|`a`| + |`b`|) @allow */
static void LC_(union_to)(struct L_(list) *const a,
struct L_(list) *const b, struct L_(list) *const result) {
PLC_(boolean)(a, b, LIST_SUBTRACTION_AB | LIST_SUBTRACTION_BA
| LIST_INTERSECTION | LIST_DEFAULT_A | LIST_DEFAULT_B, result);
}
/** Moves the intersection of `a` and `b` as sequential sorted individual
elements to `result`. Equal elements are moved from `a`. All parameters must
be unique or can be null.
For example, if `a` contains `(A, B, D)` and `b` contains `(B, C)` then
`(a:B)` would be moved to `result`.
@order \O(|`a`| + |`b`|) @allow */
static void LC_(intersection_to)(struct L_(list) *const a,
struct L_(list) *const b, struct L_(list) *const result) {
PLC_(boolean)(a, b, LIST_INTERSECTION, result);
}
/** Moves `a` exclusive-or `b` as sequential sorted individual elements to
`result`. Equal elements are moved from `a`. All parameters must be unique or
can be null.
For example, if `a` contains `(A, B, D)` and `b` contains `(B, C)` then
`(a:A, b:C, a:D)` would be moved to `result`.
@order O(|`a`| + |`b`|) @allow */
static void LC_(xor_to)(struct L_(list) *const a, struct L_(list) *const b,
struct L_(list) *const result) {
PLC_(boolean)(a, b, LIST_SUBTRACTION_AB | LIST_SUBTRACTION_BA
| LIST_DEFAULT_A | LIST_DEFAULT_B, result);
}
/** !compare(`a`, `b`) == equals(`a`, `b`).
@implements <typedef:<PLC>bipredicate_fn> */
static int PLC_(is_equal)(const struct L_(listlink) *const a,
const struct L_(listlink) *const b) { return !PLC_(compare)(a, b); }
#else /* compare --><!-- is equal */
/* Check that `LIST_IS_EQUAL` is a function implementing
<typedef:<PLC>bipredicate_fn>. */
static const PLC_(bipredicate_fn) PLC_(is_equal) = (LIST_IS_EQUAL);
#endif /* is equal --> */
/** @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 <fn:<LC>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 /* <!-- compare */
LC_(compare)(0, 0); LC_(merge)(0, 0); LC_(sort)(0);
LC_(subtraction_to)(0, 0, 0); LC_(union_to)(0, 0, 0);
LC_(intersection_to)(0, 0, 0); LC_(xor_to)(0, 0, 0);
#endif /* 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

378
src/pool.h Normal file
View File

@ -0,0 +1,378 @@
/** @license 2021 Neil Edelman, distributed under the terms of the
[MIT License](https://opensource.org/licenses/MIT).
@abstract Source <src/pool.h>, depends on <src/heap.h>, and <src/array.h>;
examples <test/test_pool.c>.
@subtitle Stable pool
![Example of Pool](../web/pool.png)
<tag:<P>pool> is a memory pool that stores <typedef:<PP>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]
`<P>` that satisfies `C` naming conventions when mangled and a valid tag type,
<typedef:<PP>type>, associated therewith; required. `<PP>` 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 <PP>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 <to_string.h>; `<PSZ>` that satisfies `C` naming
conventions when mangled and function implementing <typedef:<PSZ>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 /* <!-- idempotent */
#define POOL_H
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#if defined(POOL_CAT_) || defined(POOL_CAT) || defined(P_) || defined(PP_) \
|| defined(POOL_IDLE)
#error Unexpected defines.
#endif
/* <Kernighan and Ritchie, 1988, p. 231>. */
#define POOL_CAT_(n, m) n ## _ ## m
#define POOL_CAT(n, m) POOL_CAT_(n, m)
#define P_(n) POOL_CAT(POOL_NAME, n)
#define PP_(n) POOL_CAT(pool, P_(n))
#define POOL_IDLE { ARRAY_IDLE, HEAP_IDLE, (size_t)0 }
/** @return An order on `a`, `b` which specifies a max-heap. */
static int pool_index_compare(const size_t a, const size_t b) { return a < b; }
#define HEAP_NAME poolfree
#define HEAP_TYPE size_t
#define HEAP_COMPARE &pool_index_compare
#include "heap.h"
#endif /* idempotent --> */
#if POOL_TRAITS == 0 /* <!-- base code */
#ifndef POOL_CHUNK_MIN_CAPACITY /* <!-- !min */
#define POOL_CHUNK_MIN_CAPACITY 8
#endif /* !min --> */
#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
<fn:<P>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 <fn:<P>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 <fn:<PP>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);
}
/* <!-- iterate interface: it's not actually possible to iterate though given
the information that we have, but placing it here for testing purposes.
Iterates through the zero-slot, ignoring the free list. Do not call. */
struct PP_(iterator);
struct PP_(iterator) { struct PP_(slot) *slot0; size_t i; };
/** Loads `pool` into `it`. @implements begin */
static void PP_(begin)(struct PP_(iterator) *const it,
const struct P_(pool) *const pool) {
assert(it && pool);
if(pool->slots.size) it->slot0 = pool->slots.data + 0;
else it->slot0 = 0;
it->i = 0;
}
/** Advances `it`. @implements next */
static const PP_(type) *PP_(next)(struct PP_(iterator) *const it) {
assert(it);
return it->slot0 && it->i < it->slot0->size
? it->slot0->chunk + it->i++ : 0;
}
/* iterate --> */
#ifdef POOL_TEST /* <!-- test */
/* Forward-declare. */
static void (*PP_(to_string))(const PP_(type) *, char (*)[12]);
static const char *(*PP_(pool_to_string))(const struct P_(pool) *);
#include "../test/test_pool.h"
#endif /* test --> */
/* <!-- box (multiple traits) */
#define BOX_ PP_
#define BOX_CONTAINER struct P_(pool)
#define BOX_CONTENTS PP_(type)
static void PP_(unused_base_coda)(void);
static void PP_(unused_base)(void) {
P_(pool)(0); P_(pool_)(0); P_(pool_buffer)(0, 0); P_(pool_new)(0);
P_(pool_remove)(0, 0); P_(pool_clear)(0); PP_(begin)(0, 0);
PP_(next)(0); PP_(unused_base_coda)();
}
static void PP_(unused_base_coda)(void) { PP_(unused_base)(); }
#elif defined(POOL_TO_STRING) /* base code --><!-- to string trait */
#ifdef POOL_TO_STRING_NAME /* <!-- name */
#define SZ_(n) POOL_CAT(P_(pool), POOL_CAT(POOL_TO_STRING_NAME, n))
#else /* name --><!-- !name */
#define SZ_(n) POOL_CAT(P_(pool), n)
#endif /* !name --> */
#define TO_STRING POOL_TO_STRING
#include "to_string.h" /** \include */
#ifdef POOL_TEST /* <!-- expect: greedy satisfy forward-declared. */
#undef POOL_TEST
static PSZ_(to_string_fn) PP_(to_string) = PSZ_(to_string);
static const char *(*PP_(pool_to_string))(const struct P_(pool) *)
= &SZ_(to_string);
#endif /* expect --> */
#undef SZ_
#undef POOL_TO_STRING
#ifdef POOL_TO_STRING_NAME
#undef POOL_TO_STRING_NAME
#endif
#endif /* traits --> */
#ifdef POOL_EXPECT_TRAIT /* <!-- trait */
#undef POOL_EXPECT_TRAIT
#else /* trait --><!-- !trait */
#ifdef POOL_TEST
#error No POOL_TO_STRING traits defined for POOL_TEST.
#endif
#undef POOL_NAME
#undef POOL_TYPE
#undef BOX_
#undef BOX_CONTAINER
#undef BOX_CONTENTS
/* box (multiple traits) --> */
#endif /* !trait --> */
#undef POOL_TO_STRING_TRAIT
#undef POOL_TRAITS

988
src/table.h Normal file
View File

@ -0,0 +1,988 @@
/** @license 2019 Neil Edelman, distributed under the terms of the
[MIT License](https://opensource.org/licenses/MIT).
@abstract Source <src/table.h>; examples <test/test_table.c>.
@subtitle Hash table
![Example of <string>table.](../web/table.png)
<tag:<N>table> implements a set or map of <typedef:<PN>entry> as a hash table.
It must be supplied a <typedef:<PN>hash_fn> and, <typedef:<PN>is_equal_fn> or
<typedef:<PN>inverse_hash_fn>.
@param[TABLE_NAME, TABLE_KEY]
`<N>` that satisfies `C` naming conventions when mangled and a valid
<typedef:<PN>key> associated therewith; required. `<PN>` 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 <typedef:<PN>hash_fn>, and
<typedef:<PN>is_equal_fn> or <typedef:<PN>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 <typedef:<PN>uint>, the unsigned type of hash hash of the key given by
<typedef:<PN>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
<typedef:<PN>value> used in <fn:<N>table<D>get>. 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 <to_string.h>; `<SZ>` that satisfies `C` naming
conventions when mangled and function implementing
<typedef:<PSZ>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 /* <!-- idempotent */
#define TABLE_H
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#if defined(TABLE_CAT_) || defined(TABLE_CAT) || defined(N_) || defined(PN_) \
|| defined(TABLE_IDLE)
#error Unexpected defines.
#endif
/* <Kernighan and Ritchie, 1988, p. 231>. */
#define TABLE_CAT_(n, m) n ## _ ## m
#define TABLE_CAT(n, m) TABLE_CAT_(n, m)
#define N_(n) TABLE_CAT(TABLE_NAME, n)
#define PN_(n) TABLE_CAT(table, N_(n))
#define TABLE_IDLE { 0, 0, 0, 0 }
/* Use the sign bit to store out-of-band flags when a <typedef:<PN>uint>
represents an address in the table, (such that range of an index is one bit
less.) Choose representations that probably save power. We cannot save this in
an `enum` because we don't know maximum. */
#define TABLE_M1 ((PN_(uint))~(PN_(uint))0) /* 2's compliment -1. */
#define TABLE_HIGH ((TABLE_M1 >> 1) + 1) /* Cardinality. */
#define TABLE_END (TABLE_HIGH)
#define TABLE_NULL (TABLE_HIGH + 1)
#define TABLE_RESULT X(ERROR), X(UNIQUE), X(YIELD), X(REPLACE)
#define X(n) TABLE_##n
/** A result of modifying the table, of which `TABLE_ERROR` is false.
![A diagram of the result states.](../web/put.png) */
enum table_result { TABLE_RESULT };
#undef X
#define X(n) #n
/** A static array of strings describing the <tag:table_result>. */
static const char *const table_result_str[] = { TABLE_RESULT };
#undef X
#undef TABLE_RESULT
#endif /* idempotent --> */
#if TABLE_TRAITS == 0 /* <!-- base table */
#ifndef TABLE_UINT
#define TABLE_UINT size_t
#endif
/** <typedef:<PN>hash_fn> returns this hash type by `TABLE_UINT`, which must be
be an unsigned integer. Places a simplifying limit on the maximum number of
elements of half the cardinality. */
typedef TABLE_UINT PN_(uint);
/** Valid tag type defined by `TABLE_KEY` used for keys. If `TABLE_INVERSE` is
not defined, a copy of this value will be stored in the internal buckets. */
typedef TABLE_KEY PN_(key);
/** Read-only <typedef:<PN>key>. Makes the simplifying assumption that this is
not `const`-qualified. */
typedef const TABLE_KEY PN_(ckey);
/** A map from <typedef:<PN>ckey> onto <typedef:<PN>uint> that, ideally,
should be easy to compute while minimizing duplicate addresses. Must be
consistent for each value while in the table. If <typedef:<PN>key> is a
pointer, one is permitted to have null in the domain. */
typedef PN_(uint) (*PN_(hash_fn))(PN_(ckey));
/* Check that `TABLE_HASH` is a function implementing <typedef:<PN>hash_fn>. */
static const PN_(hash_fn) PN_(hash) = (TABLE_HASH);
#ifdef TABLE_INVERSE /* <!-- inv */
/** Defining `TABLE_INVERSE` says <typedef:<PN>hash_fn> forms a bijection
between the range in <typedef:<PN>key> and the image in <typedef:<PN>uint>.
The keys are not stored in the hash table, but they are generated from the
hashes using this inverse-mapping. */
typedef PN_(key) (*PN_(inverse_hash_fn))(PN_(uint));
/* Check that `TABLE_INVERSE` is a function implementing
<typedef:<PN>inverse_hash_fn>. */
static const PN_(inverse_hash_fn) PN_(inverse_hash) = (TABLE_INVERSE);
#else /* inv --><!-- !inv */
/** Equivalence relation between <typedef:<PN>key> that satisfies
`<PN>is_equal_fn(a, b) -> <PN>hash(a) == <PN>hash(b)`. Not used if
`TABLE_INVERSE` because the comparison is done in hash space, in that case. */
typedef int (*PN_(is_equal_fn))(PN_(ckey) a, PN_(ckey) b);
/* Check that `TABLE_IS_EQUAL` is a function implementing
<typedef:<PN>is_equal_fn>. */
static const PN_(is_equal_fn) PN_(equal) = (TABLE_IS_EQUAL);
#endif /* !inv --> */
#ifdef TABLE_VALUE /* <!-- value */
/** Defining `TABLE_VALUE` produces an associative map, otherwise it is the
same as <typedef:<PN>key>. */
typedef TABLE_VALUE PN_(value);
/** Defining `TABLE_VALUE` creates this map from <typedef:<PN>key> to
<typedef:<PN>value> as an interface with table. */
struct N_(table_entry) { PN_(key) key; PN_(value) value; };
/** If `TABLE_VALUE`, this is <tag:<N>table_entry>; otherwise, it's the same as
<typedef:<PN>key>. */
typedef struct N_(table_entry) PN_(entry);
#else /* value --><!-- !value */
typedef PN_(key) PN_(value);
typedef PN_(key) PN_(entry);
#endif /* !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 { <PN>key key; <PN>value value; } */
entry->key = PN_(bucket_key)(bucket);
memcpy(&entry->value, &bucket->value, sizeof bucket->value);
#else /* entry <PN>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 <fn:<N>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 <fn:<PN>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;
}
/* <!-- stack */
/** On return, the `top` of `table` will be empty and eager, but size is not
incremented, leaving it in intermediate state. Amortized if you grow only. */
static void PN_(grow_stack)(struct N_(table) *const table) {
/* Subtract one for eager. */
PN_(uint) top = (table->top & ~TABLE_HIGH) - !(table->top & TABLE_HIGH);
assert(table && table->buckets && table->top && top < PN_(capacity)(table));
while(table->buckets[top].next != TABLE_NULL) assert(top), top--;
table->top = top; /* Eager, since one is allegedly going to fill it. */
}
/** Force the evaluation of the stack of `table`, thereby making it eager. This
is like searching for a bucket in open-addressing. @order \O(`buckets`) */
static void PN_(force_stack)(struct N_(table) *const table) {
PN_(uint) top = table->top;
if(top & TABLE_HIGH) { /* Lazy. */
struct PN_(bucket) *bucket;
top &= ~TABLE_HIGH;
do bucket = table->buckets + ++top/*, assert(top < capacity)*/;
while(bucket->next != TABLE_NULL
&& PN_(to_bucket)(table, bucket->hash) == top);
table->top = top; /* Eager. */
}
}
/** Is `i` in `table` possibly on the stack? (The stack grows from the high.) */
static int PN_(in_stack_range)(const struct N_(table) *const table,
const PN_(uint) i) {
return assert(table && table->buckets),
(table->top & ~TABLE_HIGH) + !!(table->top & TABLE_HIGH) <= i;
}
/** Corrects newly-deleted `b` from `table` in the stack. */
static void PN_(shrink_stack)(struct N_(table) *const table,
const PN_(uint) b) {
assert(table && table->buckets && b < PN_(capacity)(table));
assert(table->buckets[b].next == TABLE_NULL);
if(!PN_(in_stack_range)(table, b)) return;
PN_(force_stack)(table); /* Only have room for 1 step of laziness. */
assert(PN_(in_stack_range)(table, b)); /* I think this is assured? Think. */
if(b != table->top) {
struct PN_(bucket) *const prev = PN_(prev)(table, table->top);
memcpy(table->buckets + b, table->buckets + table->top,
sizeof *table->buckets);
prev->next = b;
}
table->buckets[table->top].next = TABLE_NULL;
table->top |= TABLE_HIGH; /* Lazy. */
}
/** Moves the `m` index in non-idle `table`, to the top of collision stack.
This may result in an inconsistent state; one is responsible for filling that
hole and linking it with top. */
static void PN_(move_to_top)(struct N_(table) *const table, const PN_(uint) m) {
struct PN_(bucket) *move, *top, *prev;
assert(table
&& table->size < PN_(capacity)(table) && m < PN_(capacity)(table));
PN_(grow_stack)(table); /* Leaves it in an eager state. */
move = table->buckets + m, top = table->buckets + table->top;
assert(move->next != TABLE_NULL && top->next == TABLE_NULL);
if(prev = PN_(prev)(table, m)) prev->next = table->top; /* \O(|`bucket`|) */
memcpy(top, move, sizeof *move), move->next = TABLE_NULL;
}
/* stack --> */
/** `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 /* <!-- !splay */
return bucket1;
#else /* !splay --><!-- splay: bring the MRU to the front. */
if(b0 == TABLE_NULL) return bucket1;
{
struct PN_(bucket) *const bucket0 = table->buckets + b0,
*const bucket_head = table->buckets + head, temp;
bucket0->next = b2;
memcpy(&temp, bucket_head, sizeof *bucket_head);
memcpy(bucket_head, bucket1, sizeof *bucket1);
memcpy(bucket1, &temp, sizeof temp);
bucket_head->next = b1;
return bucket_head;
}
#endif /* 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 <typedef:<PN>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 <tag:table_result>. @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 /* <!-- value */
/** On `TABLE_VALUE`, try to put `key` into `table`, and update `value` to be
a pointer to the current value.
@return `TABLE_ERROR` does not set `value`; `TABLE_ABSENT`, the `value` will
be uninitialized; `TABLE_YIELD`, gets the current `value`. @throws[malloc] */
static enum table_result PN_(compute)(struct N_(table) *const table,
PN_(key) key, PN_(value) **const value) {
struct PN_(bucket) *bucket;
const PN_(uint) hash = PN_(hash)(key);
enum table_result result;
assert(table && value);
if(table->buckets && (bucket = PN_(query)(table, key, hash))) {
result = TABLE_YIELD;
} else {
if(!(bucket = PN_(evict)(table, hash))) return TABLE_ERROR;
PN_(replace_key)(bucket, key, hash);
result = TABLE_UNIQUE;
}
*value = &bucket->value;
return result;
}
#endif /* 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 <fn:<N>table_is>, otherwise, a
<typedef:<PN>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 <fn:<N>table_replace>.
@return `original` and `replace` ignored, true.
@implements <typedef:<PN>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 /* <!-- value */
/** If `TABLE_VALUE` is defined. Try to put `key` into `table`, and store the
associated value in a pointer `value`.
@return `TABLE_ERROR` does not set `value`; `TABLE_GROW`, the `value` will
point to uninitialized memory; `TABLE_YIELD`, gets the current `value` but
doesn't use the `key`. @throws[malloc, ERANGE] On `TABLE_ERROR`. @allow */
static enum table_result N_(table_compute)(struct N_(table) *const table,
PN_(key) key, PN_(value) **const value)
{ return PN_(compute)(table, key, value); }
#endif /* 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;
}
/* <!-- private iterate interface */
/* Contains all iteration parameters. */
struct PN_(iterator) {
const struct N_(table) *table;
union { const void *const do_not_warn; PN_(uint) b; } _;
};
/** Loads `table` (can be null) into `it`. @implements begin */
static void PN_(begin)(struct PN_(iterator) *const it,
const struct N_(table) *const table)
{ assert(it), it->table = table, it->_.b = 0; }
/** Helper to skip the buckets of `it` that are not there.
@return Whether it found another index. */
static int PN_(skip)(struct PN_(iterator) *const it) {
const struct N_(table) *const hash = it->table;
const PN_(uint) limit = PN_(capacity)(hash);
assert(it && it->table && it->table->buckets);
while(it->_.b < limit) {
struct PN_(bucket) *const bucket = hash->buckets + it->_.b;
if(bucket->next != TABLE_NULL) return 1;
it->_.b++;
}
return 0;
}
/** Advances `it`. @implements next */
static struct PN_(bucket) *PN_(next)(struct PN_(iterator) *const it) {
assert(it);
if(!it->table || !it->table->buckets) return 0;
if(PN_(skip)(it)) return it->table->buckets + it->_.b++;
it->table = 0, it->_.b = 0;
return 0;
}
/* iterate --> */
/** ![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 <fn:<N>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 <fn:<N>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 /* <!-- value */
/** Defined if `TABLE_VALUE`. Advances `it` only when <fn:<N>table_has_next>.
@return The next key. @allow */
static PN_(key) N_(table_next_key)(struct N_(table_iterator) *const it) {
struct PN_(bucket) *b = PN_(next)(&it->it);
return PN_(advance)(it, b), PN_(bucket_key)(b);
}
/** Defined if `TABLE_VALUE`. Advances `it` only when <fn:<N>table_has_next>.
@return The next value. @allow */
static PN_(value) N_(table_next_value)(struct N_(table_iterator) *const it) {
struct PN_(bucket) *b = PN_(next)(&it->it);
return PN_(advance)(it, b), b->value;
}
#endif /* value --> */
/** Removes the entry at `it`. Whereas <fn:<N>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;
}
/* <!-- box (multiple traits) */
#define BOX_ PN_
#define BOX_CONTAINER struct N_(table)
#define BOX_CONTENTS struct PN_(bucket)
#ifdef TABLE_TEST /* <!-- test */
/* Forward-declare. */
static void (*PN_(to_string))(PN_(ckey), char (*)[12]);
static const char *(*PN_(table_to_string))(const struct N_(table) *);
#include "../test/test_table.h"
#endif /* test --> */
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 --><!-- default */
#ifdef TABLE_DEFAULT_NAME
#define N_D_(n, m) TABLE_CAT(N_(n), TABLE_CAT(TABLE_DEFAULT_NAME, m))
#define PN_D_(n, m) TABLE_CAT(table, N_D_(n, m))
#else
#define N_D_(n, m) TABLE_CAT(N_(n), m)
#define PN_D_(n, m) TABLE_CAT(table, N_D_(n, m))
#endif
/* Check that `TABLE_DEFAULT` is a valid <tag:<PN>value> and that only one
`TABLE_DEFAULT_NAME` is omitted. */
static const PN_(value) PN_D_(default, value) = (TABLE_DEFAULT);
/** This is functionally identical to <fn:<N>table_get_or>, but a with a trait
specifying a constant default value.
@return The value associated with `key` in `table`, (which can be null.) If
no such value exists, the `TABLE_DEFAULT` is returned.
@order Average \O(1); worst \O(n). @allow */
static PN_(value) N_D_(table, get)(struct N_(table) *const table,
const PN_(key) key) {
struct PN_(bucket) *bucket;
return table && table->buckets
&& (bucket = PN_(query)(table, key, PN_(hash)(key)))
? PN_(bucket_value)(bucket) : PN_D_(default, value);
}
static void PN_D_(unused, default_coda)(void);
static void PN_D_(unused, default)(void) { PN_(key) k; memset(&k, 0, sizeof k);
N_D_(table, get)(0, k); PN_D_(unused, default_coda)(); }
static void PN_D_(unused, default_coda)(void) { PN_D_(unused, default)(); }
#undef N_D_
#undef PN_D_
#undef TABLE_DEFAULT
#ifdef TABLE_DEFAULT_NAME
#undef TABLE_DEFAULT_NAME
#endif
#elif defined(TABLE_TO_STRING) /* default --><!-- to string trait */
#ifdef TABLE_TO_STRING_NAME
#define SZ_(n) TABLE_CAT(N_(table), TABLE_CAT(TABLE_TO_STRING_NAME, n))
#else
#define SZ_(n) TABLE_CAT(N_(table), n)
#endif
#define TSZ_(n) TABLE_CAT(table_sz, SZ_(n))
/* Check that `TABLE_TO_STRING` is a function implementing this prototype. */
static void (*const TSZ_(actual_to_string))(PN_(ckey), char (*)[12])
= (TABLE_TO_STRING);
/** This is to line up the hash, which can have <typedef:<PN>key> a pointer or
not, with to string, which requires a pointer. Call
<data:<TSZ>actual_to_string> with key of `bucket` and `z`. */
static void TSZ_(thunk_to_string)(const struct PN_(bucket) *const bucket,
char (*const z)[12])
{ TSZ_(actual_to_string)(PN_(bucket_key)(bucket), z); }
#define TO_STRING &TSZ_(thunk_to_string)
#define TO_STRING_LEFT '{'
#define TO_STRING_RIGHT '}'
#include "to_string.h" /** \include */
#ifdef TABLE_TEST /* <!-- expect: greedy satisfy forward-declared. */
#undef TABLE_TEST
static void (*PN_(to_string))(PN_(ckey), char (*const)[12])
= TSZ_(actual_to_string);
static const char *(*PN_(table_to_string))(const struct N_(table) *)
= &SZ_(to_string);
#endif /* expect --> */
#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 /* <!-- trait */
#undef TABLE_EXPECT_TRAIT
#else /* trait --><!-- !trait */
#ifdef TABLE_TEST
#error No TABLE_TO_STRING traits defined for TABLE_TEST.
#endif
#undef TABLE_NAME
#undef TABLE_KEY
#undef TABLE_UINT
#undef TABLE_HASH
#ifdef TABLE_IS_EQUAL
#undef TABLE_IS_EQUAL
#else
#undef TABLE_INVERSE
#endif
#ifdef TABLE_VALUE
#undef TABLE_VALUE
#endif
#undef BOX_
#undef BOX_CONTAINER
#undef BOX_CONTENTS
/* box (multiple traits) --> */
#endif /* !trait --> */
#undef TABLE_DEFAULT_TRAIT
#undef TABLE_TO_STRING_TRAIT
#undef TABLE_TRAITS

156
src/to_string.h Normal file
View File

@ -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 <typedef:<PZ>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)) /* <!-- not */
#error Should be the on the first to_string in the compilation unit.
#else /* not --><!-- !not */
#if defined(TO_STRING_EXTERN) && defined(TO_STRING_INTERN) /* <!-- two */
#error These can not be defined together.
#endif /* two --> */
#endif /* !not --> */
#ifndef TO_STRING_H /* <!-- idempotent */
#define TO_STRING_H
#include <string.h>
#if defined(TO_STRING_CAT_) || defined(TO_STRING_CAT) || defined(PSZ_)
#error Unexpected defines.
#endif
/* <Kernighan and Ritchie, 1988, p. 231>. */
#define TO_STRING_CAT_(n, m) n ## _ ## m
#define TO_STRING_CAT(n, m) TO_STRING_CAT_(n, m)
#define PSZ_(n) TO_STRING_CAT(to_string, SZ_(n))
#if defined(TO_STRING_EXTERN) || defined(TO_STRING_INTERN) /* <!-- ntern */
extern char to_string_buffers[4][256];
extern const unsigned to_string_buffers_no;
extern unsigned to_string_i;
#ifdef TO_STRING_INTERN /* <!-- intern */
char to_string_buffers[4][256];
const unsigned to_string_buffers_no = sizeof to_string_buffers
/ sizeof *to_string_buffers, to_string_buffer_size
= sizeof *to_string_buffers / sizeof **to_string_buffers;
unsigned to_string_buffer_i;
#endif /* intern --> */
#else /* ntern --><!-- static */
static char to_string_buffers[4][256];
static const unsigned to_string_buffers_no = sizeof to_string_buffers
/ sizeof *to_string_buffers, to_string_buffer_size
= sizeof *to_string_buffers / sizeof **to_string_buffers;
static unsigned to_string_buffer_i;
#endif /* static --> */
#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);
/** <to_string.h>: responsible for turning the argument into a 12-`char`
null-terminated output string. `<PSZ>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
<typedef:<PSZ>to_string>. */
static const PSZ_(to_string_fn) PSZ_(to_string) = (TO_STRING);
/** <src/to_string.h>: print the contents of `box` in a static string buffer of
256 bytes, with limitations of only printing 4 things at a time. `<PSZ>box` is
contracted to be the box itself. `<SZ>` is loosely contracted to be a name
`<X>box[<X_TO_STRING_NAME>]`.
@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