Bible.
This commit is contained in:
parent
4122fc3407
commit
78cdeafc8b
128
src/array.h
128
src/array.h
@ -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);
|
||||
|
123
src/interpret.c
123
src/interpret.c
@ -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);
|
||||
}
|
||||
|
@ -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), \
|
||||
|
@ -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;
|
||||
|
434
src/list.h
434
src/list.h
@ -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
|
378
src/pool.h
378
src/pool.h
@ -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
|
988
src/table.h
988
src/table.h
@ -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
|
@ -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
1094
src/tree.h
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user