Way overcomplicated.
This commit is contained in:
parent
58b228e9d0
commit
b87906bb49
396
src/array.h
Normal file
396
src/array.h
Normal 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
437
src/array_coda.h
Normal 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
432
src/heap.h
Normal 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
|
@ -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
434
src/list.h
Normal 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
406
src/list_coda.h
Normal 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
378
src/pool.h
Normal 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
988
src/table.h
Normal 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
156
src/to_string.h
Normal 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
|
Loading…
Reference in New Issue
Block a user