This commit is contained in:
Neil 2022-07-06 10:02:28 -07:00
parent 4122fc3407
commit 78cdeafc8b
9 changed files with 960 additions and 2201 deletions

View File

@ -110,90 +110,67 @@ static int PA_(is_element_c)(PA_(type_c) *const x) { return !!x; }
/* Enumerate the contents (`input_or_output_const_iterator`.)
@implements `forward` */
struct PA_(forward) { const struct A_(array) *a; size_t next; };
/** @return Before `a`. @implements `forward_begin` */
static struct PA_(forward) PA_(forward_begin)(const struct A_(array) *const a) {
/** @return A pointer to null in `a`. @implements `forward` */
static struct PA_(forward) PA_(forward)(const struct A_(array) *const a) {
struct PA_(forward) it; it.a = a, it.next = 0; return it; }
/** Move to next `it`. @return Element or null. @implements `forward_next` */
static PA_(type_c) *PA_(forward_next)(struct PA_(forward) *const it)
{ return assert(it), it->a && it->next < it->a->size
? it->a->data + it->next++ : 0; }
/** Move to next `it`. @return Element or null. @implements `next_c` */
static PA_(type_c) *PA_(next_c)(struct PA_(forward) *const it) {
assert(it);
if(it->a && it->next < it->a->size) return it->a->data + it->next++;
else { it->next = 0; return 0; }
}
#define BOX_ITERATOR PA_(type) *
/** Is `x` not null? @implements `is_element` */
static int PA_(is_element)(const PA_(type) *const x) { return !!x; }
/* More complex iterator that supports bi-directional movement and write. The
cursor is half way between elements, `cur = cursor - 0.5`, pointing left
(`dir` false) or right (`dir` true). @implements `iterator` */
struct PA_(iterator) { struct A_(array) *a; size_t cur; int dir; };
/** @return Before `a`. @implements `begin` */
static struct PA_(iterator) PA_(begin)(struct A_(array) *const a)
{ struct PA_(iterator) it; it.a = a, it.cur = 0, it.dir = 0; return it; }
/** After `a`. @implements `end` */
static struct PA_(iterator) PA_(end)(struct A_(array) *const a)
{ struct PA_(iterator) it; it.a = a, it.cur = a ? a->size : 0, it.dir = 1;
return it; }
/** Move to next `it`. @return Element or null. @implements `next` */
static PA_(type) *PA_(next)(struct PA_(iterator) *const it) {
size_t size;
PA_(type) *element;
assert(it);
if(!it->a || !(size = it->a->size)) { /* Null or empty. */
it->cur = 0, it->dir = 0, element = 0;
} else if(!it->dir) { /* Left. */
it->dir = 1;
if(it->cur < size) element = it->a->data + it->cur;
else it->cur = size, element = 0; /* Ended by prev-next. */
} else if(size <= it->cur) { /* Right and already ended. */
it->cur = size, element = 0;
} else { /* Right. */
if(++it->cur < size) element = it->a->data + it->cur;
else element = 0; /* Just ended. */
}
return element;
/* @implements `iterator` */
struct PA_(iterator) { struct A_(array) *a; size_t i; int seen; };
/** @return A pointer to null in `a`. @implements `iterator` */
static struct PA_(iterator) PA_(iterator)(struct A_(array) *const a) {
struct PA_(iterator) it; it.a = a, it.i = 0, it.seen = 0;
return it;
}
/** Move to previous `it`. @return Element or null. @implements `previous` */
static PA_(type) *PA_(previous)(struct PA_(iterator) *const it) {
size_t size;
PA_(type) *element;
/** Move to next `it`. @return Element or null on end. @implements `next` */
static PA_(type) *PA_(next)(struct PA_(iterator) *const it) {
size_t i;
assert(it);
if(!it->a || !(size = it->a->size)) { /* Null or empty. */
it->cur = 0, it->dir = 0, element = 0;
} else if(it->dir) { /* Right. */
it->dir = 0;
/* Clip. */
if(size < it->cur) element = it->a->data + (it->cur = size) - 1;
else if(it->cur) element = it->a->data + it->cur - 1;
else element = 0; /* Ended by next-prev. */
} else if(!it->cur) { /* Left and already ended. */
element = 0;
} else { /* Left. */
if(size < it->cur) element = it->a->data + (it->cur = size) - 1;
else if(--it->cur) element = it->a->data + it->cur - 1;
else element = 0; /* Just ended. */
if(!it->a || (i = it->i + !!it->seen) >= it->a->size)
{ *it = PA_(iterator)(it->a); return 0; }
return it->a->data + (it->seen = 1, it->i = i);
}
/** Move to previous `it`. @return Element or null on end.
@implements `previous` */
static PA_(type) *PA_(previous)(struct PA_(iterator) *const it) {
size_t i, size;
assert(it);
if(!it->a || !(size = it->a->size)) goto reset;
if(i = it->i) {
if(i > size) i = size;
i--;
} else {
if(!it->seen) i = it->a->size - 1;
else goto reset;
}
return element;
return it->a->data + (it->seen = 1, it->i = i);
reset:
*it = PA_(iterator)(it->a);
return 0;
}
/** Removes the element last returned by `it`. (Untested.)
@return There was an element. @order \O(`a.size`). @implements `remove` */
static int PA_(remove)(struct PA_(iterator) *const it) {
assert(0 && it);
if(!it->dir || !it->a) return 0;
if(it->dir) {
if(it->a->size <= it->cur) return 0;
} else {
if(!it->cur || it->a->size < it->cur) return 0;
it->cur--;
}
memmove(it->a->data + it->cur, it->a->data + it->cur + 1,
sizeof *it->a->data * (--it->a->size - it->cur));
assert(0 && 1);
if(!it->a || !it->seen || it->a->size <= it->i) return 0;
memmove(it->a->data + it->i, it->a->data + it->i + 1,
sizeof *it->a->data * (--it->a->size - it->i));
return 1;
}
#define BOX_ACCESS
/** @return Iterator immediately before element `idx` of `a`.
@implements `index` */
static struct PA_(iterator) PA_(index)(struct A_(array) *a, size_t idx)
{ struct PA_(iterator) it; it.a = a, it.cur = idx, it.dir = 0; return it; }
@implements `before` */
static struct PA_(iterator) PA_(before)(struct A_(array) *a, size_t idx)
{ struct PA_(iterator) it; it.a = a, it.i = idx, it.seen = 0; return it; }
/** Size of `a`. @implements `size` */
static size_t PA_(size)(const struct A_(array) *a) { return a ? a->size : 0; }
/** @return Element `idx` of `a`. @implements `at` */
@ -223,16 +200,13 @@ static struct A_(array) A_(array)(void)
static void A_(array_)(struct A_(array) *const a)
{ if(a) free(a->data), *a = A_(array)(); }
/** @return A cursor before the front of `a`. */
static struct A_(array_iterator) A_(array_begin)(struct A_(array) *a)
{ struct A_(array_iterator) it; it._ = PA_(begin)(a); return it; }
/** @return An iterator after the end of `a`. */
static struct A_(array_iterator) A_(array_end)(struct A_(array) *a)
{ struct A_(array_iterator) it; it._ = PA_(end)(a); return it; }
/** @return An iterator of `a`. */
static struct A_(array_iterator) A_(array_iterator)(struct A_(array) *a)
{ struct A_(array_iterator) it; it._ = PA_(iterator)(a); return it; }
/** @return An iterator at `idx` of `a`. */
static struct A_(array_iterator) A_(array_index)(struct A_(array) *a,
static struct A_(array_iterator) A_(array_iterator_before)(struct A_(array) *a,
size_t idx) { struct A_(array_iterator) it;
it._ = PA_(index)(a, idx); return it; }
it._ = PA_(before)(a, idx); return it; }
/** @return `it` next element. */
static PA_(type) *A_(array_next)(struct A_(array_iterator) *const it)
{ return assert(it), PA_(next)(&it->_); }
@ -410,11 +384,11 @@ static const char *(*PA_(array_to_string))(const struct A_(array) *);
static void PA_(unused_base_coda)(void);
static void PA_(unused_base)(void) {
PA_(is_element_c)(0); PA_(forward_begin)(0); PA_(forward_next)(0);
PA_(is_element_c)(0); PA_(forward)(0); PA_(next_c)(0);
PA_(is_element)(0); PA_(remove)(0); PA_(size)(0); PA_(at)(0, 0);
PA_(tell_size)(0, 0);
A_(array)(); A_(array_)(0);
A_(array_begin)(0); A_(array_end)(0); A_(array_index)(0, 0);
A_(array_iterator)(0); A_(array_iterator_before)(0, 0);
A_(array_previous)(0); A_(array_next)(0); A_(array_previous)(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);

View File

@ -13,8 +13,8 @@
#if INT_MAX >= 100000000000
#error int_to_string requires truncation on this compiler.
#endif
static void int_to_string(const int *const n,
char (*const a)[12]) { sprintf(*a, "%d", *n); }
static void int_to_string(const int *const n, char (*const a)[12])
{ sprintf(*a, "%d", *n); }
#define ARRAY_NAME int
#define ARRAY_TYPE int
#define ARRAY_EXPECT_TRAIT
@ -67,7 +67,8 @@ static int leap(int y) {
if(!(y % 4)) return 1;
return 0;
}
/** Not defined for some implementations. C11 */
/** Assumes: reverse ordering of byte-fields; unsigned is defined; C11 and GNU
anonymous unions. */
union date32 {
uint32_t u32;
struct { unsigned day : 5, month : 4, year : 23; };
@ -83,8 +84,7 @@ static union date32 date_to_32(const int y, const int m, const int d) {
union date32 d32 = { 0 };
/* Leap year calculations only work at y>=1 and Gregorian Calendar and max
23 bits. */
if(y < 1582 || y > 8388607 || m < 1 || m > 12 || d < 1 || d > 31)
goto no;
if(y < 1582 || y > 8388607 || m < 1 || m > 12 || d < 1 || d > 31) goto no;
switch(m) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12: break;
case 4: case 6: case 9: case 11: if(d > 30) goto no; break;
@ -104,6 +104,7 @@ static unsigned weekday(union date32 d) {
}
/* Contained in <lex.h> to share with <lex.re_c.c>. */
#define ARRAY_NAME lex
#define ARRAY_TYPE struct lex
#include "array.h"
@ -126,6 +127,97 @@ static void entry_to_string(const struct page_tree_entry_c entry,
struct source { char *key, *desc; };
/*
### plot with steps
reset session
$Data <<EOD
1,1,0
1,2,0
1,3,0
1,4,2
1,5,1
1,6,3
1,7,3
1,8,1
1,9,3
1,10,8
1,11,1
1,12,0
1,13,3
EOD
set title "Cumulative count" font ",16"
set xlabel "episode"
set ylabel "cumulative count"
set xtics 1
set key bottom right
set grid
unset border
set datafile separator comma
plot $Data u 2:($3) smooth cumulative with steps lw 2 lc "red" ti "cumulative count"
### end of code
*/
static int bible_graph(/*const*/ struct page_tree *const journal) {
enum { CHILL, BOOK, CHAPTER, WORD, NEXT } state = CHILL;
struct page_tree_entry entry = { 0, 0 };
struct lex *lex = 0;
size_t count = 0;
for(struct page_tree_iterator p_it = page_tree_iterator(journal);
(entry = page_tree_next(&p_it)).key; ) {
struct page *const page = entry.value;
for(struct lex_array_iterator l_it = lex_array_iterator(&page->lexx);
(lex = lex_array_next(&l_it)); ) {
switch(lex->symbol) {
case BIBLE_BOOK:
if(state != CHILL && state != WORD) goto catch;
if(state == WORD) printf("\n");
fprintf(stderr, "%d-%.2d-%.2d: %.*s ",
entry.key->year, entry.key->month, entry.key->day,
(int)(lex->s1 - lex->s0), lex->s0);
state = BOOK;
break;
case BIBLE_CHAPTER_VERSE:
if(state != BOOK) goto catch;
printf("%.*s -- \"", (int)(lex->s1 - lex->s0), lex->s0);
state = CHAPTER;
break;
case BIBLE_NEXT:
if(state != WORD) goto catch;
printf("\"\n");
break;
case BIBLE_TEXT:
if(state != WORD && state != CHAPTER && state != NEXT)
goto catch;
printf("%s%.*s", state == WORD ? "*" : "",
(int)(lex->s1 - lex->s0), lex->s0);
count++;
state = WORD;
break;
default:
if(state != CHILL && state != WORD) goto catch;
if(state == WORD) printf("\"\n"), state = CHILL;
break;
}
}
if(state != CHILL && state != WORD) goto catch;
if(state == WORD) printf("\n"), state = CHILL;
}
printf("Count: %lu.\n", (unsigned long)count);
return 1;
catch:
fprintf(stderr, "Bible error.\n");
if(entry.key) {
fprintf(stderr, "On date: %d-%.2d-%.2d.\n",
entry.key->year, entry.key->month, entry.key->day);
if(lex) fprintf(stderr, "At line %lu.\n", (unsigned long)lex->line);
}
errno = EILSEQ;
return 0;
}
int main(int argc, char **argv) {
int success = EXIT_SUCCESS;
char *intent = 0;
@ -157,7 +249,7 @@ int main(int argc, char **argv) {
closedir(dir), dir = 0;
/* Sort the years for sensible ordering of parsing. */
qsort(years.data, years.size, sizeof *years.data, &void_int_cmp);
fprintf(stderr, "(Files in %s: %s.)\n", argv[1], int_array_to_string(&years));
fprintf(stderr, "(In %s: %s.)\n", argv[1], int_array_to_string(&years));
/* Go though each year. */
for(y = years.data, y_end = y + years.size; y < y_end; y++) {
@ -178,8 +270,7 @@ int main(int argc, char **argv) {
}
closedir(dir), dir = 0;
qsort(months.data, months.size, sizeof *months.data, &void_int_cmp);
fprintf(stderr, "(Files in %s: %s.)\n",
fn, int_array_to_string(&months));
fprintf(stderr, "(In %s: %s.)\n", fn, int_array_to_string(&months));
/* Go though each month. */
for(m = months.data, m_end = m + months.size; m < m_end; m++) {
@ -200,8 +291,7 @@ int main(int argc, char **argv) {
}
closedir(dir), dir = 0;
qsort(days.data, days.size, sizeof *days.data, &void_int_cmp);
fprintf(stderr, "(Files in %s: %s.)\n",
fn, int_array_to_string(&days));
fprintf(stderr, "(In %s: %s.)\n", fn, int_array_to_string(&days));
for(d = days.data, d_end = d + days.size; d < d_end; d++) {
struct lex *lex = 0;
@ -251,11 +341,16 @@ syntax:
int_array_clear(&months);
if(chdir("..") == -1) goto catch;
//break; /* fixme */
/* fixme: Expand, contact is the next thing that it doesn't get. */
if(*y == 1996) break;
}
page_tree_bulk_finish(&journal);
int_array_(&years), int_array_(&months), int_array_(&days);
printf("Journal has entries: %s\n", page_tree_to_string(&journal));
fprintf(stderr, "Journal has entries: %s\n", page_tree_to_string(&journal));
/* Do something interesting? */
if(!bible_graph(&journal)) goto catch;
goto finally;
catch:
success = EXIT_FAILURE;
@ -265,12 +360,12 @@ finally:
if(dir && closedir(dir)) success = EXIT_FAILURE, perror("dir");
int_array_(&years), int_array_(&months), int_array_(&days);
struct page_tree_entry entry;
for(struct page_tree_iterator it = page_tree_begin(&journal);
for(struct page_tree_iterator it = page_tree_iterator(&journal);
(entry = page_tree_next(&it)).key; ) {
struct page *const page = entry.value;
char z[12];
date32_to_string(*entry.key, &z);
printf("Freeing %s.\n", z);
/*printf("Freeing %s.\n", z);*/
lex_array_(&page->lexx);
char_array_(&page->entry);
}

View File

@ -15,7 +15,7 @@ int lex_looks_like_day(const char *);
/* Edicts. */ \
X(SOURCE), X(DEFAULT), X(SOURCE_RECALL), \
X(LOCATION), X(LOCATION_SAVE), X(LOCATION_RECALL), \
X(SIGNIFICANT), X(SIGNIFICANT_RECALL), \
X(SIGNIFICANT), X(SIGNIFICANT_RECALL), X(EDITORIALIZING), \
\
/* Arguments. */ \
X(ARG_KEYWORD), X(ARG_DATE), X(ARG_NATURAL), X(ARG_FREEFORM), \

View File

@ -216,6 +216,14 @@ scan:
scan.is_ws_expected = 1, scan.is_source = 0;
return x->symbol = DEFAULT, 1; }
<edict> "ed"
{ if(scan.is_ws_expected || scan.edict.size)
return x->symbol = SYNTAX, 0;
scan.is_ws_expected = 1; /* no idea, just copy; probably should do sth */
scan.edict.size = 1;
scan.edict.expect[0] = EXPECT_FREEFORM;
return x->symbol = EDITORIALIZING, 1; }
<edict> "significant"
{ if(scan.is_ws_expected || scan.edict.size)
return x->symbol = SYNTAX, 0;

View File

@ -1,434 +0,0 @@
/** @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

View File

@ -1,378 +0,0 @@
/** @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

View File

@ -1,988 +0,0 @@
/** @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

View File

@ -106,9 +106,9 @@ static const char *STR_(to_string)(const PSTR_(box) *const box) {
&& 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;
it = BOX_(forward_begin)(box);
it = BOX_(forward)(box);
*b++ = left;
while(BOX_(is_element_c)(x = BOX_(forward_next)(&it))) {
while(BOX_(is_element_c)(x = BOX_(next_c)(&it))) {
PSTR_(to_string)(x, (char (*)[12])b);
/* Paranoid about '\0'. */
for(advance = 0; *b != '\0' && advance < 11; b++, advance++);
@ -116,7 +116,7 @@ static const char *STR_(to_string)(const PSTR_(box) *const box) {
/* Greedy typesetting: enough for "XXXXXXXXXXX" "," "…" ")" "\0". */
if((size_t)(b - buffer)
> to_string_buffer_size - 11 - 1 - ellipsis_len - 1 - 1)
if(BOX_(is_element_c)(BOX_(forward_next)(&it))) goto ellipsis;
if(BOX_(is_element_c)(BOX_(next_c)(&it))) goto ellipsis;
else break;
}
if(is_sep) b -= 2;

1094
src/tree.h

File diff suppressed because it is too large Load Diff