From 78cdeafc8b0417297c5d5237879f2c5cddf934a6 Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 6 Jul 2022 10:02:28 -0700 Subject: [PATCH] Bible. --- src/array.h | 128 +++--- src/interpret.c | 123 +++++- src/lex.h | 2 +- src/lex.re_c.c | 8 + src/list.h | 434 ------------------- src/pool.h | 378 ---------------- src/table.h | 988 ------------------------------------------ src/to_string.h | 6 +- src/tree.h | 1094 ++++++++++++++++++++++++++++++++++------------- 9 files changed, 960 insertions(+), 2201 deletions(-) delete mode 100644 src/list.h delete mode 100644 src/pool.h delete mode 100644 src/table.h diff --git a/src/array.h b/src/array.h index 69aa958..616d680 100644 --- a/src/array.h +++ b/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); diff --git a/src/interpret.c b/src/interpret.c index a4c0c91..286fdac 100644 --- a/src/interpret.c +++ b/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 to share with . */ #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 <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); } diff --git a/src/lex.h b/src/lex.h index c2e3b35..343eb88 100644 --- a/src/lex.h +++ b/src/lex.h @@ -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), \ diff --git a/src/lex.re_c.c b/src/lex.re_c.c index aa363da..9f8c638 100644 --- a/src/lex.re_c.c +++ b/src/lex.re_c.c @@ -216,6 +216,14 @@ scan: scan.is_ws_expected = 1, scan.is_source = 0; return x->symbol = DEFAULT, 1; } + "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; } + "significant" { if(scan.is_ws_expected || scan.edict.size) return x->symbol = SYNTAX, 0; diff --git a/src/list.h b/src/list.h deleted file mode 100644 index 7884db1..0000000 --- a/src/list.h +++ /dev/null @@ -1,434 +0,0 @@ -/** @license 2017 Neil Edelman, distributed under the terms of the - [MIT License](https://opensource.org/licenses/MIT). - - @abstract Source ; examples . - - @subtitle Doubly-linked component - - ![Example of a stochastic skip-list.](../web/list.png) - - In parlance of , list> is a circular - header, or sentinel, to a doubly-linked list of listlink>. This is a - closed structure, such that with with a pointer to any element, it is possible - to extract the entire list. - - @param[LIST_NAME] - `` that satisfies `C` naming conventions when mangled; required. `` is - private, whose names are prefixed in a manner to avoid collisions. - - @param[LIST_EXPECT_TRAIT] - Do not un-define certain variables for subsequent inclusion in a trait. - - @param[LIST_COMPARE_NAME, LIST_COMPARE, LIST_IS_EQUAL] - Compare trait contained in . An optional mangled name for - uniqueness and a function implementing either compare_fn> or - bipredicate_fn>. - - @param[LIST_TO_STRING_NAME, LIST_TO_STRING] - To string trait contained in . An optional mangled name for - uniqueness and function implementing to_string_fn>. - - @std C89 */ - -#ifndef LIST_NAME -#error Name LIST_NAME undefined. -#endif -#if defined(LIST_TO_STRING_NAME) || defined(LIST_TO_STRING) /* */ -#if defined(LIST_COMPARE_NAME) || defined(LIST_COMPARE) \ - || defined(LIST_IS_EQUAL) /* */ -#define LIST_TRAITS LIST_TO_STRING_TRAIT + LIST_COMPARE_TRAIT -#if LIST_TRAITS > 1 -#error Only one trait per include is allowed; use LIST_EXPECT_TRAIT. -#endif -#if LIST_TRAITS && !defined(LIST_BASE) -#error Trying to define a trait without defining the base datatype. -#endif -#if defined(LIST_TO_STRING_NAME) && !defined(LIST_TO_STRING) -#error LIST_TO_STRING_NAME requires LIST_TO_STRING. -#endif -#if defined(LIST_COMPARE_NAME) \ - && (!(!defined(LIST_COMPARE) ^ !defined(LIST_IS_EQUAL))) -#error LIST_COMPARE_NAME requires LIST_COMPARE or LIST_IS_EQUAL not both. -#endif - -#ifndef LIST_H /* */ - - -#if LIST_TRAITS == 0 /* */ - -/* */ - -static void PL_(unused_base_coda)(void); -static void PL_(unused_base)(void) { - L_(list_head)(0); L_(list_tail)(0); L_(list_previous)(0); L_(list_next)(0); - L_(list_clear)(0); L_(list_add_before)(0, 0); L_(list_add_after)(0, 0); - L_(list_unshift)(0, 0); L_(list_push)(0, 0); L_(list_remove)(0); - L_(list_shift)(0); L_(list_pop)(0); L_(list_to)(0, 0); - L_(list_to_before)(0, 0); L_(list_to_if)(0, 0, 0); L_(list_for_each)(0, 0); - L_(list_any)(0, 0); L_(list_self_correct)(0); - PL_(begin)(0, 0); PL_(next)(0); PL_(unused_base_coda)(); -} -static void PL_(unused_base_coda)(void) { PL_(unused_base)(); } - - -#elif defined(LIST_TO_STRING) /* base code --> */ -#define TO_STRING LIST_TO_STRING -#include "to_string.h" /** \include */ -#ifdef LIST_TEST /* */ -#undef SZ_ -#undef LIST_TO_STRING -#ifdef LIST_TO_STRING_NAME -#undef LIST_TO_STRING_NAME -#endif - - -#else /* to string trait --> */ -#include "list_coda.h" /** \include */ -#ifdef LIST_TEST /* */ -#undef LC_ -#ifdef LIST_COMPARE_NAME -#undef LIST_COMPARE_NAME -#endif -#ifdef LIST_COMPARE -#undef LIST_COMPARE -#endif -#ifdef LIST_IS_EQUAL -#undef LIST_IS_EQUAL -#endif - - -#endif /* traits --> */ - - -#ifdef LIST_EXPECT_TRAIT /* */ -#endif /* !trait --> */ -#undef LIST_TO_STRING_TRAIT -#undef LIST_COMPARE_TRAIT -#undef LIST_TRAITS diff --git a/src/pool.h b/src/pool.h deleted file mode 100644 index a9f7d31..0000000 --- a/src/pool.h +++ /dev/null @@ -1,378 +0,0 @@ -/** @license 2021 Neil Edelman, distributed under the terms of the - [MIT License](https://opensource.org/licenses/MIT). - - @abstract Source , depends on , and ; - examples . - - @subtitle Stable pool - - ![Example of Pool](../web/pool.png) - - pool> is a memory pool that stores type>. Pointers to - valid items in the pool are stable, but not generally in any order. When - removal is ongoing and uniformly sampled while reaching a steady-state size, - it will eventually settle in one contiguous region. - - @param[POOL_NAME, POOL_TYPE] - `

` that satisfies `C` naming conventions when mangled and a valid tag type, - type>, associated therewith; required. `` is private, whose - names are prefixed in a manner to avoid collisions. - - @param[POOL_CHUNK_MIN_CAPACITY] - Default is 8; optional number in - `[2, (SIZE_MAX - sizeof pool_chunk) / sizeof type]` that the capacity can - not go below. - - @param[POOL_EXPECT_TRAIT] - Do not un-define certain variables for subsequent inclusion in a trait. - - @param[POOL_TO_STRING_NAME, POOL_TO_STRING] - To string trait contained in ; `` that satisfies `C` naming - conventions when mangled and function implementing to_string_fn>. - There can be multiple to string traits, but only one can omit - `POOL_TO_STRING_NAME`. This container is only partially iterable: the values - are only the first chunk, so this is not very useful except for debugging. - - @depend [array](https://github.com/neil-edelman/array) - @depend [heap](https://github.com/neil-edelman/heap) - @std C89 */ - -#if !defined(POOL_NAME) || !defined(POOL_TYPE) -#error Name POOL_NAME undefined or tag type POOL_TYPE undefined. -#endif -#if defined(POOL_TO_STRING_NAME) || defined(POOL_TO_STRING) -#define POOL_TO_STRING_TRAIT 1 -#else -#define POOL_TO_STRING_TRAIT 0 -#endif -#define POOL_TRAITS POOL_TO_STRING_TRAIT -#if POOL_TRAITS > 1 -#error Only one trait per include is allowed; use POOL_EXPECT_TRAIT. -#endif -#if defined(POOL_TO_STRING_NAME) && !defined(POOL_TO_STRING) -#error POOL_TO_STRING_NAME requires POOL_TO_STRING. -#endif - -#ifndef POOL_H /* */ - - -#if POOL_TRAITS == 0 /* */ -#if POOL_CHUNK_MIN_CAPACITY < 2 -#error Pool chunk capacity error. -#endif - -/** A valid tag type set by `POOL_TYPE`. */ -typedef POOL_TYPE PP_(type); - -/* Size and chunk, which goes into a sorted array. */ -struct PP_(slot) { size_t size; PP_(type) *chunk; }; -#define ARRAY_NAME PP_(slot) -#define ARRAY_TYPE struct PP_(slot) -#include "array.h" - -/** Consists of a map of several chunks of increasing size and a free-heap. - Zeroed data is a valid state. To instantiate to an idle state, see - pool>, `POOL_IDLE`, `{0}` (`C99`,) or being `static`. - - ![States.](../web/states.png) */ -struct P_(pool) { - struct PP_(slot_array) slots; - struct poolfree_heap free0; /* Free-list in chunk-zero. */ - size_t capacity0; /* Capacity of chunk-zero. */ -}; - -/** @return Index of sorted chunk that is higher than `x` in `slots`, but - treating zero as special. @order \O(\log `chunks`) */ -static size_t PP_(upper)(const struct PP_(slot_array) *const slots, - const PP_(type) *const x) { - const struct PP_(slot) *const base = slots->data; - size_t n, b0, b1; - assert(slots && x); - if(!(n = slots->size)) return 0; - assert(base); - if(!--n) return 1; - /* The last one is a special case: it doesn't have an upper bound. */ - for(b0 = 1, --n; n; n /= 2) { - b1 = b0 + n / 2; - if(x < base[b1].chunk) { continue; } - else if(base[b1 + 1].chunk <= x) { b0 = b1 + 1; n--; continue; } - else { return b1 + 1; } - } - return b0 + (x >= base[slots->size - 1].chunk); -} - -/** Which chunk is `x` in `pool`? - @order \O(\log `chunks`), \O(\log \log `size`)? */ -static size_t PP_(chunk_idx)(const struct P_(pool) *const pool, - const PP_(type) *const x) { - struct PP_(slot) *const base = pool->slots.data; - size_t up; - assert(pool && pool->slots.size && base && x); - /* One chunk, assume it's in that chunk; first chunk is `capacity0`. */ - if(pool->slots.size <= 1 - || (x >= base[0].chunk && x < base[0].chunk + pool->capacity0)) - return assert(x >= base[0].chunk && x < base[0].chunk + pool->capacity0), - 0; - up = PP_(upper)(&pool->slots, x); - return assert(up), up - 1; -} - -/** Makes sure there are space for `n` further items in `pool`. - @return Success. */ -static int PP_(buffer)(struct P_(pool) *const pool, const size_t n) { - const size_t min_size = POOL_CHUNK_MIN_CAPACITY, - max_size = (size_t)-1 / sizeof(PP_(type)); - struct PP_(slot) *base = pool->slots.data, *slot; - PP_(type) *chunk; - size_t c, insert; - int is_recycled = 0; - assert(pool && min_size <= max_size && pool->capacity0 <= max_size && - (!pool->slots.size && !pool->free0.a.size /* !chunks[0] -> !free0 */ - || pool->slots.size && base - && base[0].size <= pool->capacity0 - && (!pool->free0.a.size - || pool->free0.a.size < base[0].size - && pool->free0.a.data[0] < base[0].size))); - - /* Ensure space for new slot. */ - if(!n || pool->slots.size && n <= pool->capacity0 - - base[0].size + pool->free0.a.size) return 1; /* Already enough. */ - if(max_size < n) return errno = ERANGE, 1; /* Request unsatisfiable. */ - if(!PP_(slot_array_buffer)(&pool->slots, 1)) return 0; - base = pool->slots.data; /* It may have moved! */ - - /* Figure out the capacity of the next chunk. */ - c = pool->capacity0; - if(pool->slots.size && base[0].size) { /* ~Golden ratio. */ - size_t c1 = c + (c >> 1) + (c >> 3); - c = (c1 < c || c1 > max_size) ? max_size : c1; - } - if(c < min_size) c = min_size; - if(c < n) c = n; - - /* Allocate it. */ - if(pool->slots.size && !base[0].size) - is_recycled = 1, chunk = realloc(base[0].chunk, c * sizeof *chunk); - else chunk = malloc(c * sizeof *chunk); - if(!chunk) { if(!errno) errno = ERANGE; return 0; } - pool->capacity0 = c; /* We only need to store the capacity of chunk 0. */ - if(is_recycled) return base[0].size = 0, base[0].chunk = chunk, 1; - - /* Evict chunk 0. */ - if(!pool->slots.size) insert = 0; - else insert = PP_(upper)(&pool->slots, base[0].chunk); - assert(insert <= pool->slots.size); - slot = PP_(slot_array_insert)(&pool->slots, 1, insert); - assert(slot); /* Made space for it before. */ - slot->chunk = base[0].chunk, slot->size = base[0].size; - base[0].chunk = chunk, base[0].size = 0; - return 1; -} - -/** Either `data` in `pool` is in a secondary chunk, in which case it - decrements the size, or it's the zero-chunk, where it gets added to the - free-heap. - @return Success. It may fail due to a free-heap memory allocation error. - @order Amortized \O(\log \log `items`) @throws[realloc] */ -static int PP_(remove)(struct P_(pool) *const pool, - const PP_(type) *const data) { - size_t c = PP_(chunk_idx)(pool, data); - struct PP_(slot) *slot = pool->slots.data + c; - assert(pool && pool->slots.size && data); - if(!c) { /* It's in the zero-slot, we need to deal with the free-heap. */ - const size_t idx = (size_t)(data - slot->chunk); - assert(pool->capacity0 && slot->size <= pool->capacity0 - && idx < slot->size); - if(idx + 1 == slot->size) { - /* Keep shrinking going while item on the free-heap are exposed. */ - while(--slot->size && !poolfree_heap_is_empty(&pool->free0)) { - const size_t free = poolfree_heap_peek(&pool->free0); - if(free < slot->size - 1) break; - assert(free == slot->size - 1); - poolfree_heap_pop(&pool->free0); - } - } else if(!poolfree_heap_add(&pool->free0, idx)) return 0; - } else if(assert(slot->size), !--slot->size) { - PP_(type) *const chunk = slot->chunk; - PP_(slot_array_remove)(&pool->slots, pool->slots.data + c); - free(chunk); - } - return 1; -} - -/** Initializes `pool` to idle. @order \Theta(1) @allow */ -static void P_(pool)(struct P_(pool) *const pool) { assert(pool), - PP_(slot_array)(&pool->slots), poolfree_heap(&pool->free0), - pool->capacity0 = 0; } - -/** Destroys `pool` and returns it to idle. @order \O(\log `data`) @allow */ -static void P_(pool_)(struct P_(pool) *const pool) { - struct PP_(slot) *s, *s_end; - assert(pool); - for(s = pool->slots.data, s_end = s + pool->slots.size; s < s_end; s++) - assert(s->chunk), free(s->chunk); - PP_(slot_array_)(&pool->slots); - poolfree_heap_(&pool->free0); - P_(pool)(pool); -} - -/** Ensure capacity of at least `n` further items in `pool`. Pre-sizing is - better for contiguous blocks, but takes up that memory. - @return Success. @throws[ERANGE, malloc] @allow */ -static int P_(pool_buffer)(struct P_(pool) *const pool, const size_t n) { - return assert(pool), PP_(buffer)(pool, n); -} - -/** This pointer is constant until it gets pool_remove>. - @return A pointer to a new uninitialized element from `pool`. - @throws[ERANGE, malloc] @order amortised O(1) @allow */ -static PP_(type) *P_(pool_new)(struct P_(pool) *const pool) { - struct PP_(slot) *slot0; - assert(pool); - if(!PP_(buffer)(pool, 1)) return 0; - assert(pool->slots.size && (pool->free0.a.size || - pool->slots.data[0].size < pool->capacity0)); - if(!poolfree_heap_is_empty(&pool->free0)) { - /* Cheating: we prefer the minimum index from a max-heap, but it - doesn't really matter, so take the one off the array used for heap. */ - size_t *free; - free = heap_poolfree_node_array_pop(&pool->free0.a); - return assert(free), pool->slots.data[0].chunk + *free; - } - /* The free-heap is empty; guaranteed by buffer>. */ - slot0 = pool->slots.data + 0; - assert(slot0 && slot0->size < pool->capacity0); - return slot0->chunk + slot0->size++; -} - -/** Deletes `data` from `pool`. Do not remove data that is not in `pool`. - @return Success. @order \O(\log \log `items`) @allow */ -static int P_(pool_remove)(struct P_(pool) *const pool, - PP_(type) *const data) { return PP_(remove)(pool, data); } - -/** Removes all from `pool`, but keeps it's active state, only freeing the - smaller blocks. @order \O(\log `items`) @allow */ -static void P_(pool_clear)(struct P_(pool) *const pool) { - struct PP_(slot) *s, *s_end; - assert(pool); - if(!pool->slots.size) { assert(!pool->free0.a.size); return; } - for(s = pool->slots.data + 1, s_end = s - 1 + pool->slots.size; - s < s_end; s++) assert(s->chunk && s->size), free(s->chunk); - pool->slots.data[0].size = 0; - pool->slots.size = 1; - poolfree_heap_clear(&pool->free0); -} - -/* */ - -#ifdef POOL_TEST /* */ - -/* */ -#define TO_STRING POOL_TO_STRING -#include "to_string.h" /** \include */ -#ifdef POOL_TEST /* */ -#undef SZ_ -#undef POOL_TO_STRING -#ifdef POOL_TO_STRING_NAME -#undef POOL_TO_STRING_NAME -#endif - - -#endif /* traits --> */ - - -#ifdef POOL_EXPECT_TRAIT /* */ -#endif /* !trait --> */ -#undef POOL_TO_STRING_TRAIT -#undef POOL_TRAITS diff --git a/src/table.h b/src/table.h deleted file mode 100644 index 2b8469f..0000000 --- a/src/table.h +++ /dev/null @@ -1,988 +0,0 @@ -/** @license 2019 Neil Edelman, distributed under the terms of the - [MIT License](https://opensource.org/licenses/MIT). - - @abstract Source ; examples . - - @subtitle Hash table - - ![Example of table.](../web/table.png) - - table> implements a set or map of entry> as a hash table. - It must be supplied a hash_fn> and, is_equal_fn> or - inverse_hash_fn>. - - @param[TABLE_NAME, TABLE_KEY] - `` that satisfies `C` naming conventions when mangled and a valid - key> associated therewith; required. `` is private, whose - names are prefixed in a manner to avoid collisions. - - @param[TABLE_HASH, TABLE_IS_EQUAL, TABLE_INVERSE] - `TABLE_HASH`, and either `TABLE_IS_EQUAL` or `TABLE_INVERSE`, but not both, - are required. Function satisfying hash_fn>, and - is_equal_fn> or inverse_hash_fn>. - - @param[TABLE_VALUE] - An optional type that is the payload of the key, thus making this a map or - associative array. (If the key is part of an aggregate pointer, it will be - more efficient and robust to use a set with a type conversion instead of - storing related pointers in a map.) - - @param[TABLE_UINT] - This is uint>, the unsigned type of hash hash of the key given by - hash_fn>; defaults to `size_t`. - - @param[TABLE_EXPECT_TRAIT] - Do not un-define certain variables for subsequent inclusion in a trait. - - @param[TABLE_DEFAULT_NAME, TABLE_DEFAULT] - Default trait; a name that satisfies `C` naming conventions when mangled and a - value> used in tableget>. There can be multiple - defaults, but only one can omit `TABLE_DEFAULT_NAME`. - - @param[TABLE_TO_STRING_NAME, TABLE_TO_STRING] - To string trait contained in ; `` that satisfies `C` naming - conventions when mangled and function implementing - to_string_fn>. There can be multiple to string traits, but only - one can omit `TABLE_TO_STRING_NAME`. - - @std C89 */ - -#if !defined(TABLE_NAME) || !defined(TABLE_KEY) || !defined(TABLE_HASH) \ - || !(defined(TABLE_IS_EQUAL) ^ defined(TABLE_INVERSE)) -#error Name TABLE_NAME, tag type TABLE_KEY, functions TABLE_HASH, and, \ - TABLE_IS_EQUAL or TABLE_INVERSE (but not both) undefined. -#endif -#if defined(TABLE_DEFAULT_NAME) || defined(TABLE_DEFAULT) -#define TABLE_DEFAULT_TRAIT 1 -#else -#define TABLE_DEFAULT_TRAIT 0 -#endif -#if defined(TABLE_TO_STRING_NAME) || defined(TABLE_TO_STRING) -#define TABLE_TO_STRING_TRAIT 1 -#else -#define TABLE_TO_STRING_TRAIT 0 -#endif -#define TABLE_TRAITS TABLE_DEFAULT_TRAIT + TABLE_TO_STRING_TRAIT -#if TABLE_TRAITS > 1 -#error Only one trait per include is allowed; use TABLE_EXPECT_TRAIT. -#endif -#if defined(TABLE_DEFAULT_NAME) && !defined(TABLE_DEFAULT) -#error TABLE_DEFAULT_NAME requires TABLE_DEFAULT. -#endif -#if defined(TABLE_TO_STRING_NAME) && !defined(TABLE_TO_STRING) -#error TABLE_TO_STRING_NAME requires TABLE_TO_STRING. -#endif - -#ifndef TABLE_H /* */ - - -#if TABLE_TRAITS == 0 /* */ - -#ifdef TABLE_VALUE /* */ - -/** @return Key from `e`. */ -static PN_(key) PN_(entry_key)(PN_(entry) e) { -#ifdef TABLE_VALUE - return e.key; -#else - return e; -#endif -} - -/* Address is hash modulo size of table. Any occupied buckets at the head of - the linked structure are closed, that is, the address equals the index. These - form a linked table, possibly with other, open buckets that have the same - address in vacant buckets. */ -struct PN_(bucket) { - PN_(uint) next; /* Bucket index, including `TABLE_NULL` and `TABLE_END`. */ - PN_(uint) hash; -#ifndef TABLE_INVERSE - PN_(key) key; -#endif -#ifdef TABLE_VALUE - PN_(value) value; -#endif -}; - -/** Gets the key of an occupied `bucket`. */ -static PN_(key) PN_(bucket_key)(const struct PN_(bucket) *const bucket) { - assert(bucket && bucket->next != TABLE_NULL); -#ifdef TABLE_INVERSE - return PN_(inverse_hash)(bucket->hash); -#else - return bucket->key; -#endif -} - -/** Gets the value of an occupied `bucket`, which might be the same as the - key. */ -static PN_(value) PN_(bucket_value)(const struct PN_(bucket) *const bucket) { - assert(bucket && bucket->next != TABLE_NULL); -#ifdef TABLE_VALUE - return bucket->value; -#else - return PN_(bucket_key)(bucket); -#endif -} - -/** Fills `entry`, a public structure, with the information of `bucket`. */ -static void PN_(to_entry)(const struct PN_(bucket) *const bucket, - PN_(entry) *const entry) { - assert(bucket && entry); -#ifdef TABLE_VALUE /* entry { key key; value value; } */ - entry->key = PN_(bucket_key)(bucket); - memcpy(&entry->value, &bucket->value, sizeof bucket->value); -#else /* entry key */ - *entry = PN_(bucket_key)(bucket); -#endif -} - -/** Returns true if the `replace` replaces the `original`. */ -typedef int (*PN_(policy_fn))(PN_(key) original, PN_(key) replace); - -/** To initialize, see table>, `TABLE_IDLE`, `{0}` (`C99`,) or being - `static`. The fields should be treated as read-only; any modification is - liable to cause the table to go into an invalid state. - - ![States.](../web/states.png) */ -struct N_(table) { /* "Padding size," good. */ - struct PN_(bucket) *buckets; /* @ has zero/one key specified by `next`. */ - /* `size <= capacity`; size is not needed but convenient and allows - short-circuiting. Index of the top of the stack; however, we are really - lazy, so MSB store is the top a step ahead? Thereby, hysteresis. */ - PN_(uint) log_capacity, size, top; -}; - -/** The capacity of a non-idle `table` is always a power-of-two. */ -static PN_(uint) PN_(capacity)(const struct N_(table) *const table) - { return assert(table && table->buckets && table->log_capacity >= 3), - (PN_(uint))((PN_(uint))1 << table->log_capacity); } - -/** @return Indexes the first closed bucket in the set of buckets with the same - address from non-idle `table` given the `hash`. If the bucket is empty, it - will have `next = TABLE_NULL` or it's own to_bucket> not equal to the - index. */ -static PN_(uint) PN_(to_bucket)(const struct N_(table) *const table, - const PN_(uint) hash) { return hash & (PN_(capacity)(table) - 1); } - -/** @return Search for the previous link in the bucket to `b` in `table`, if it - exists, (by restarting and going though the list.) @order \O(`bucket size`) */ -static struct PN_(bucket) *PN_(prev)(const struct N_(table) *const table, - const PN_(uint) b) { - const struct PN_(bucket) *const bucket = table->buckets + b; - PN_(uint) to_next = TABLE_NULL, next; - assert(table && bucket->next != TABLE_NULL); - /* Note that this does not check for corrupted tables; would get assert. */ - for(next = PN_(to_bucket)(table, bucket->hash); - /* assert(next < capacity), */ next != b; - to_next = next, next = table->buckets[next].next); - return to_next != TABLE_NULL ? table->buckets + to_next : 0; -} - -/* */ - -/** `TABLE_INVERSE` is injective, so in that case, we only compare hashes. - @return `a` and `b`. */ -static int PN_(equal_buckets)(PN_(ckey) a, PN_(ckey) b) { -#ifdef TABLE_INVERSE - return (void)a, (void)b, 1; -#else - return PN_(equal)(a, b); -#endif -} - -/** `table` will be searched linearly for `key` which has `hash`. - @fixme Move to front like splay trees? */ -static struct PN_(bucket) *PN_(query)(struct N_(table) *const table, - PN_(ckey) key, const PN_(uint) hash) { - struct PN_(bucket) *bucket1; - PN_(uint) head, b0 = TABLE_NULL, b1, b2; - assert(table && table->buckets && table->log_capacity); - bucket1 = table->buckets + (head = b1 = PN_(to_bucket)(table, hash)); - /* Not the start of a bucket: empty or in the collision stack. */ - if((b2 = bucket1->next) == TABLE_NULL - || PN_(in_stack_range)(table, b1) - && b1 != PN_(to_bucket)(table, bucket1->hash)) return 0; - while(hash != bucket1->hash - || !PN_(equal_buckets)(key, PN_(bucket_key)(bucket1))) { - if(b2 == TABLE_END) return 0; - bucket1 = table->buckets + (b0 = b1, b1 = b2); - assert(b1 < PN_(capacity)(table) && PN_(in_stack_range)(table, b1) - && b1 != TABLE_NULL); - b2 = bucket1->next; - } -#ifdef TABLE_DONT_SPLAY /* */ -} - -/** Ensures that `table` has enough buckets to fill `n` more than the size. May - invalidate and re-arrange the order. - @return Success; otherwise, `errno` will be set. @throws[realloc] - @throws[ERANGE] Tried allocating more then can fit in half uint> - or `realloc` doesn't follow [POSIX - ](https://pubs.opengroup.org/onlinepubs/009695399/functions/realloc.html). */ -static int PN_(buffer)(struct N_(table) *const table, const PN_(uint) n) { - struct PN_(bucket) *buckets; - const PN_(uint) log_c0 = table->log_capacity, - c0 = log_c0 ? (PN_(uint))((PN_(uint))1 << log_c0) : 0; - PN_(uint) log_c1, c1, size1, i, wait, mask; - assert(table && table->size <= TABLE_HIGH - && (!table->buckets && !table->size && !log_c0 && !c0 - || table->buckets && table->size <= c0 && log_c0>=3)); - /* Can we satisfy `n` growth from the buffer? */ - if(TABLE_M1 - table->size < n || TABLE_HIGH < (size1 = table->size + n)) - return errno = ERANGE, 0; - if(table->buckets) log_c1 = log_c0, c1 = c0 ? c0 : 1; - else log_c1 = 3, c1 = 8; - while(c1 < size1) log_c1++, c1 <<= 1; - if(log_c0 == log_c1) return 1; - - /* Otherwise, need to allocate more. */ - if(!(buckets = realloc(table->buckets, sizeof *buckets * c1))) - { if(!errno) errno = ERANGE; return 0; } - table->top = (c1 - 1) | TABLE_HIGH; /* No stack. */ - table->buckets = buckets, table->log_capacity = log_c1; - - /* Initialize new values. Mask to identify the added bits. */ - { struct PN_(bucket) *e = buckets + c0, *const e_end = buckets + c1; - for( ; e < e_end; e++) e->next = TABLE_NULL; } - mask = (PN_(uint))((((PN_(uint))1 << log_c0) - 1) - ^ (((PN_(uint))1 << log_c1) - 1)); - - /* Rehash most closed buckets in the lower half. Create waiting - linked-stack by borrowing next. */ - wait = TABLE_END; - for(i = 0; i < c0; i++) { - struct PN_(bucket) *idx, *go; - PN_(uint) g, hash; - idx = table->buckets + i; - if(idx->next == TABLE_NULL) continue; - g = PN_(to_bucket)(table, hash = idx->hash); - /* It's a power-of-two size, so, like consistent hashing, `E[old/new]` - capacity that a closed bucket will remain where it is. */ - if(i == g) { idx->next = TABLE_END; continue; } - if((go = table->buckets + g)->next == TABLE_NULL) { - /* Priority is given to the first closed bucket; simpler later. */ - struct PN_(bucket) *head; - PN_(uint) h = g & ~mask; assert(h <= g); - if(h < g && i < h - && (head = table->buckets + h, assert(head->next != TABLE_NULL), - PN_(to_bucket)(table, head->hash) == g)) { - memcpy(go, head, sizeof *head); - go->next = TABLE_END, head->next = TABLE_NULL; - /* Fall-though -- the bucket still needs to be put on wait. */ - } else { - /* If the new bucket is available and this bucket is first. */ - memcpy(go, idx, sizeof *idx); - go->next = TABLE_END, idx->next = TABLE_NULL; - continue; - } - } - idx->next = wait, wait = i; /* Push for next sweep. */ - } - - /* Search waiting stack for buckets that moved concurrently. */ - { PN_(uint) prev = TABLE_END, w = wait; while(w != TABLE_END) { - struct PN_(bucket) *waiting = table->buckets + w; - PN_(uint) cl = PN_(to_bucket)(table, waiting->hash); - struct PN_(bucket) *const closed = table->buckets + cl; - assert(cl != w); - if(closed->next == TABLE_NULL) { - memcpy(closed, waiting, sizeof *waiting), closed->next = TABLE_END; - if(prev != TABLE_END) table->buckets[prev].next = waiting->next; - if(wait == w) wait = waiting->next; /* First, modify head. */ - w = waiting->next, waiting->next = TABLE_NULL; - } else { - assert(closed->next == TABLE_END); /* Not in the wait stack. */ - prev = w, w = waiting->next; - } - }} - - /* Rebuild the top stack at the high numbers from the waiting at low. */ - while(wait != TABLE_END) { - struct PN_(bucket) *const waiting = table->buckets + wait; - PN_(uint) h = PN_(to_bucket)(table, waiting->hash); - struct PN_(bucket) *const head = table->buckets + h; - struct PN_(bucket) *top; - assert(h != wait && head->next != TABLE_NULL); - PN_(grow_stack)(table), top = table->buckets + table->top; - memcpy(top, waiting, sizeof *waiting); - top->next = head->next, head->next = table->top; - wait = waiting->next, waiting->next = TABLE_NULL; /* Pop. */ - } - - return 1; -} - -/** Replace the `key` and `hash` of `bucket`. Don't touch next. */ -static void PN_(replace_key)(struct PN_(bucket) *const bucket, - const PN_(key) key, const PN_(uint) hash) { - (void)key; - bucket->hash = hash; -#ifndef TABLE_INVERSE - memcpy(&bucket->key, &key, sizeof key); -#endif -} - -/** Replace the entire `entry` and `hash` of `bucket`. Don't touch next. */ -static void PN_(replace_entry)(struct PN_(bucket) *const bucket, - const PN_(entry) entry, const PN_(uint) hash) { - PN_(replace_key)(bucket, PN_(entry_key)(entry), hash); -#ifdef TABLE_VALUE - memcpy(&bucket->value, &entry.value, sizeof(entry.value)); -#endif -} - -/** Evicts the spot where `hash` goes in `table`. This results in a space in - the table. */ -static struct PN_(bucket) *PN_(evict)(struct N_(table) *const table, - const PN_(uint) hash) { - PN_(uint) i; - struct PN_(bucket) *bucket; - if(!PN_(buffer)(table, 1)) return 0; /* Amortized. */ - bucket = table->buckets + (i = PN_(to_bucket)(table, hash)); /* Closed. */ - if(bucket->next != TABLE_NULL) { /* Occupied. */ - int in_stack = PN_(to_bucket)(table, bucket->hash) != i; - PN_(move_to_top)(table, i); - bucket->next = in_stack ? TABLE_END : table->top; - } else { /* Unoccupied. */ - bucket->next = TABLE_END; - } - table->size++; - return bucket; -} - -/** Put `entry` in `table`. For collisions, only if `policy` exists and returns - true do and displace it to `eject`, if non-null. - @return A . @throws[malloc] - @order Amortized \O(max bucket length); the key to another bucket may have to - be moved to the top; the table might be full and have to be resized. */ -static enum table_result PN_(put)(struct N_(table) *const table, - PN_(entry) entry, PN_(entry) *eject, const PN_(policy_fn) policy) { - struct PN_(bucket) *bucket; - const PN_(key) key = PN_(entry_key)(entry); - const PN_(uint) hash = PN_(hash)(key); - enum table_result result; - assert(table); - if(table->buckets && (bucket = PN_(query)(table, key, hash))) { - if(!policy || !policy(PN_(bucket_key)(bucket), key)) return TABLE_YIELD; - if(eject) PN_(to_entry)(bucket, eject); - result = TABLE_REPLACE; - } else { - if(!(bucket = PN_(evict)(table, hash))) return TABLE_ERROR; - result = TABLE_UNIQUE; - } - PN_(replace_entry)(bucket, entry, hash); - return result; -} - -#ifdef TABLE_VALUE /* */ - -/** Initialises `table` to idle. @order \Theta(1) @allow */ -static void N_(table)(struct N_(table) *const table) { - assert(table); - table->buckets = 0; - table->log_capacity = 0; table->size = 0; table->top = 0; -} - -/** Destroys `table` and returns it to idle. @allow */ -static void N_(table_)(struct N_(table) *const table) - { assert(table), free(table->buckets); N_(table)(table); } - -/** Reserve at least `n` more empty buckets in `table`. This may cause the - capacity to increase and invalidates any pointers to data in the table. - @return Success. - @throws[ERANGE] The request was unsatisfiable. @throws[realloc] @allow */ -static int N_(table_buffer)(struct N_(table) *const table, const PN_(uint) n) - { return assert(table), PN_(buffer)(table, n); } - -/** Clears and removes all buckets from `table`. The capacity and memory of the - `table` is preserved, but all previous values are un-associated. (The load - factor will be less until it reaches it's previous size.) - @order \Theta(`table.capacity`) @allow */ -static void N_(table_clear)(struct N_(table) *const table) { - struct PN_(bucket) *b, *b_end; - assert(table); - if(!table->buckets) { assert(!table->log_capacity); return; } - assert(table->log_capacity); - for(b = table->buckets, b_end = b + PN_(capacity)(table); b < b_end; b++) - b->next = TABLE_NULL; - table->size = 0; - table->top = (PN_(capacity)(table) - 1) & TABLE_HIGH; -} - -/** @return Whether `key` is in `table` (which can be null.) @allow */ -static int N_(table_is)(struct N_(table) *const table, const PN_(key) key) - { return table && table->buckets - ? !!PN_(query)(table, key, PN_(hash)(key)) : 0; } - -/** @param[result] If null, behaves like table_is>, otherwise, a - entry> which gets filled on true. - @return Whether `key` is in `table` (which can be null.) @allow */ -static int N_(table_query)(struct N_(table) *const table, const PN_(key) key, - PN_(entry) *const result) { - struct PN_(bucket) *bucket; - if(!table || !table->buckets - || !(bucket = PN_(query)(table, key, PN_(hash)(key)))) return 0; - if(result) PN_(to_entry)(bucket, result); - return 1; -} - -/** @return The value associated with `key` in `table`, (which can be null.) If - no such value exists, `default_value` is returned. - @order Average \O(1); worst \O(n). @allow */ -static PN_(value) N_(table_get_or)(struct N_(table) *const table, - const PN_(key) key, PN_(value) default_value) { - struct PN_(bucket) *bucket; - return table && table->buckets - && (bucket = PN_(query)(table, key, PN_(hash)(key))) - ? PN_(bucket_value)(bucket) : default_value; -} - -/** Puts `entry` in `table` only if absent. - @return One of: `TABLE_ERROR`, the table is not modified; `TABLE_YIELD`, not - modified if there is another entry with the same key; `TABLE_UNIQUE`, put an - entry in the table. - @throws[realloc, ERANGE] On `TABLE_ERROR`. - @order Average amortised \O(1); worst \O(n). @allow */ -static enum table_result N_(table_try)(struct N_(table) *const table, - PN_(entry) entry) { return PN_(put)(table, entry, 0, 0); } - -/** Callback in table_replace>. - @return `original` and `replace` ignored, true. - @implements policy_fn> */ -static int PN_(always_replace)(const PN_(key) original, - const PN_(key) replace) { return (void)original, (void)replace, 1; } - -/** Puts `entry` in `table`. - @return One of: `TABLE_ERROR`, the table is not modified; `TABLE_REPLACE`, the - `entry` is put if the table, and, if non-null, `eject` will be filled; - `TABLE_UNIQUE`, on a unique entry. - @throws[realloc, ERANGE] On `TABLE_ERROR`. - @order Average amortised \O(1); worst \O(n). @allow */ -static enum table_result N_(table_replace)(struct N_(table) *const table, - PN_(entry) entry, PN_(entry) *eject) { - return PN_(put)(table, entry, eject, &PN_(always_replace)); -} - -/** Puts `entry` in `table` only if absent or if calling `policy` returns true. - @return One of: `TABLE_ERROR`, the table is not modified; `TABLE_REPLACE`, if - `update` is non-null and returns true, if non-null, `eject` will be filled; - `TABLE_YIELD`, if `update` is null or false; `TABLE_UNIQUE`, on unique entry. - @throws[realloc, ERANGE] On `TABLE_ERROR`. - @order Average amortised \O(1); worst \O(n). @allow */ -static enum table_result N_(table_update)(struct N_(table) *const table, - PN_(entry) entry, PN_(entry) *eject, const PN_(policy_fn) policy) - { return PN_(put)(table, entry, eject, policy); } - -#ifdef TABLE_VALUE /* */ - -/** Removes `key` from `table` (which could be null.) - @return Whether that `key` was in `table`. @order Average \O(1), (hash - distributes elements uniformly); worst \O(n). @allow */ -static int N_(table_remove)(struct N_(table) *const table, - const PN_(key) key) { - struct PN_(bucket) *current; - PN_(uint) crnt, prv = TABLE_NULL, nxt, hash = PN_(hash)(key); - if(!table || !table->size) return 0; assert(table->buckets); - /* Find item and keep track of previous. */ - current = table->buckets + (crnt = PN_(to_bucket)(table, hash)); - if((nxt = current->next) == TABLE_NULL - || PN_(in_stack_range)(table, crnt) - && crnt != PN_(to_bucket)(table, current->hash)) return 0; - while(hash != current->hash - && !PN_(equal_buckets)(key, PN_(bucket_key)(current))) { - if(nxt == TABLE_END) return 0; - prv = crnt, current = table->buckets + (crnt = nxt); - assert(crnt < PN_(capacity)(table) && PN_(in_stack_range)(table, crnt) - && crnt != TABLE_NULL); - nxt = current->next; - } - if(prv != TABLE_NULL) { /* Open entry. */ - struct PN_(bucket) *previous = table->buckets + prv; - previous->next = current->next; - } else if(current->next != TABLE_END) { /* Head closed entry and others. */ - struct PN_(bucket) *const second - = table->buckets + (crnt = current->next); - assert(current->next < PN_(capacity)(table)); - memcpy(current, second, sizeof *second); - current = second; - } - current->next = TABLE_NULL, table->size--, PN_(shrink_stack)(table, crnt); - return 1; -} - -/* */ - -/** ![States](../web/it.png) - - Adding, deleting, successfully looking up entries, or any modification of the - table's topology invalidates the iterator. - Iteration usually not in any particular order. The asymptotic runtime of - iterating though the whole table is proportional to the capacity. */ -struct N_(table_iterator); -struct N_(table_iterator) { struct PN_(iterator) it; - struct N_(table) *modify; union { PN_(uint) prev; void *do_not_warn; } _; }; - -/** Loads `table` (can be null) into `it`. @allow */ -static void N_(table_begin)(struct N_(table_iterator) *const it, - struct N_(table) *const table) { - PN_(begin)(&it->it, table); - /* Stupid: I want to have table_iterator_remove>; so I need a - non-constant value. The value in to string is constant. */ - it->modify = table; - it->_.prev = TABLE_NULL; -} - -/** Advances `prev` to keep up with `b` in `it`. - (Stupid: changing back to offset.) */ -static void PN_(advance)(struct N_(table_iterator) *const it, - const struct PN_(bucket) *const b) - { it->_.prev = (PN_(uint))(b - it->it.table->buckets); } - -/** Advances `it`. - @param[entry] If non-null, the entry is filled with the next element only if - it has a next. @return Whether it had a next element. @allow */ -static int N_(table_next)(struct N_(table_iterator) *const it, - PN_(entry) *entry) { - const struct PN_(bucket) *b = PN_(next)(&it->it); - return b ? (PN_(advance)(it, b), PN_(to_entry)(b, entry), 1) : 0; -} - -/** Especially for tables that can have zero as a valid value, this is used to - differentiate between zero and null. - @return Whether the table specified to `it` in table_begin> has a - next element. @order Amortized on the capacity, \O(1). @allow */ -static int N_(table_has_next)(struct N_(table_iterator) *const it) { - assert(it); - return it->it.table && it->it.table->buckets && PN_(skip)(&it->it); -} - -#ifdef TABLE_VALUE /* */ - -/** Removes the entry at `it`. Whereas table_remove> invalidates the - iterator, this corrects for a signal `it`. - @return Success, or there was no entry at the iterator's position, (anymore.) - @allow */ -static int N_(table_iterator_remove)(struct N_(table_iterator) *const it) { - struct N_(table) *table; - PN_(uint) b = it->_.prev; - struct PN_(bucket) *previous = 0, *current; - PN_(uint) prv = TABLE_NULL, crnt; - assert(it); - if(b == TABLE_NULL) return 0; - table = it->modify; - assert(table && table == it->it.table - && table->buckets && b < PN_(capacity)(table)); - /* Egregious code reuse. :[ */ - current = table->buckets + b, assert(current->next != TABLE_NULL); - crnt = PN_(to_bucket)(table, current->hash); - while(crnt != b) assert(crnt < PN_(capacity)(table)), - crnt = (previous = table->buckets + (prv = crnt))->next; - if(prv != TABLE_NULL) { /* Open entry. */ - previous->next = current->next; - } else if(current->next != TABLE_END) { /* Head closed entry and others. */ - const PN_(uint) scnd = current->next; - struct PN_(bucket) *const second = table->buckets + scnd; - assert(scnd < PN_(capacity)(table)); - memcpy(current, second, sizeof *second); - if(crnt < scnd) it->it._.b = it->_.prev; /* Iterate new entry. */ - crnt = scnd; current = second; - } - current->next = TABLE_NULL, table->size--, PN_(shrink_stack)(table, crnt); - it->_.prev = TABLE_NULL; - return 1; -} - -/* */ - -static void PN_(unused_base_coda)(void); -static void PN_(unused_base)(void) { - PN_(entry) e; PN_(key) k; PN_(value) v; - memset(&e, 0, sizeof e); memset(&k, 0, sizeof k); memset(&v, 0, sizeof v); - N_(table)(0); N_(table_)(0); N_(table_buffer)(0, 0); N_(table_clear)(0); - N_(table_is)(0, k); N_(table_query)(0, k, 0); N_(table_get_or)(0, k, v); - N_(table_try)(0, e); N_(table_replace)(0, e, 0); N_(table_update)(0,e,0,0); - N_(table_remove)(0, 0); N_(table_begin)(0, 0); N_(table_next)(0, 0); - N_(table_has_next)(0); N_(table_iterator_remove)(0);PN_(unused_base_coda)(); -#ifdef TABLE_VALUE - N_(table_compute)(0, k, 0); N_(table_next_key)(0); N_(table_next_value)(0); -#endif -} -static void PN_(unused_base_coda)(void) { PN_(unused_base)(); } - - -#elif defined(TABLE_DEFAULT) /* base --> */ -#undef TSZ_ -#undef SZ_ -#undef TABLE_TO_STRING -#ifdef TABLE_TO_STRING_NAME -#undef TABLE_TO_STRING_NAME -#endif - - -#endif /* traits --> */ - - -#ifdef TABLE_EXPECT_TRAIT /* */ -#endif /* !trait --> */ -#undef TABLE_DEFAULT_TRAIT -#undef TABLE_TO_STRING_TRAIT -#undef TABLE_TRAITS diff --git a/src/to_string.h b/src/to_string.h index f300c5c..e5cde77 100644 --- a/src/to_string.h +++ b/src/to_string.h @@ -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; diff --git a/src/tree.h b/src/tree.h index d2c4da5..f0b5c49 100644 --- a/src/tree.h +++ b/src/tree.h @@ -4,9 +4,11 @@ @abstract Stand-alone header ; examples . On a compatible workstation, `make` creates the test suite of the examples. - @subtitle Ordered tree + @subtitle Ordered key-tree - A tree> is an ordered set or map. + A tree> is an ordered set or map contained in a tree. For memory + locality, this is implemented B-tree, described in + . @param[TREE_NAME, TREE_KEY] `` that satisfies `C` naming conventions when mangled, required, and @@ -31,7 +33,7 @@ that satisfies `C` naming conventions when mangled and function implementing to_string_fn>. - @fixme multi-key; implementation of order statistic tree + @fixme multi-key; implementation of order statistic tree? @fixme merge, difference @std C89 */ @@ -69,16 +71,18 @@ #define TREE_CAT(n, m) TREE_CAT_(n, m) #define B_(n) TREE_CAT(TREE_NAME, n) #define PB_(n) TREE_CAT(tree, B_(n)) -/* Leaf: `TREE_MAX type`; branch: `TREE_MAX type + TREE_ORDER pointer`. */ -#define TREE_MAX 2 +/* Leaf: `TREE_MAX type`; branch: `TREE_MAX type + TREE_ORDER pointer`. In + , these are (a,b)-trees as + (TREE_MIN+1,TREE_MAX+1)-trees. */ +#define TREE_MAX 5 #if TREE_MAX < 2 || TREE_MAX > UCHAR_MAX #error TREE_MAX parameter range `[3, UCHAR_MAX]`. #endif /* This is the worst-case branching factor; the performance will be \O(log_{`TREE_MIN`+1} `size`). Usually this is `⌈(TREE_MAX+1)/2⌉-1`. However, - smaller values are less-eager; this has been chosen to provide hysteresis. In - the extreme, show good results. (Except - `TREE_MAX 2`, one can be the only value.) */ + smaller values are less-eager; in the extreme, + , show good results; this has been + chosen to provide hysteresis. (Except `TREE_MAX 2`, it's fixed.) */ #define TREE_MIN (TREE_MAX / 3 ? TREE_MAX / 3 : 1) #if TREE_MIN == 0 || TREE_MIN > TREE_MAX / 2 #error TREE_MIN parameter range `[1, \floor(TREE_MAX / 2)]`. @@ -96,6 +100,7 @@ enum tree_result { TREE_RESULT }; static const char *const tree_result_str[] = { TREE_RESULT }; #undef X #undef TREE_RESULT +struct tree_count { size_t branches, leaves; }; #endif /* idempotent --> */ @@ -134,11 +139,10 @@ static int PB_(default_compare)(PB_(key_c) a, PB_(key_c) b) compare_fn>, if defined. */ static const PB_(compare_fn) PB_(compare) = (TREE_COMPARE); -/* B-tree node, as . These rules are more lazy - than the original so as to not exhibit worst-case behaviour in small trees, as - , but lookup is potentially slower after - deleting; this is a design decision that nodes are not cached. In the - terminology of , +/* These rules are more lazy than the original so as to not exhibit worst-case + behaviour in small trees, as , (lookup + is potentially slower after deleting.) In the terminology of + , * Every branch has at most `TREE_ORDER == TREE_MAX + 1` children, which is at minimum three. * Every non-root and non-bulk-loaded node has at least `TREE_MIN` keys, @@ -147,14 +151,13 @@ static const PB_(compare_fn) PB_(compare) = (TREE_COMPARE); is a consequence of the fact that they are implicitly storing a complete binary sub-tree.) * All leaves are at the maximum depth and height zero; they do'n't carry links - to other nodes. (The height is one less then the original paper, as - , for computational simplicity.) + to other nodes, (hence, leaf.) In this code, a branch node is a + specialization of a (leaf) node with children. One can tell if it's a branch + by keeping track of the height. * There are two empty B-trees to facilitate allocation hysteresis between 0 -- 1: idle `{ 0, 0 }`, and `{ garbage leaf, UINT_MAX }`, one could test, `!root || height == UINT_MAX`. - * Bulk-loading always is on the right side. - * A branch node is a specialization of a (leaf) node with children. One can - tell if it's a branch by the non-zero height. */ + * Bulk-loading always is on the right side. */ struct PB_(node) { unsigned char size; /* `[0, TREE_MAX]`. */ PB_(key) key[TREE_MAX]; /* Cache-friendly lookup. */ @@ -164,26 +167,30 @@ struct PB_(node) { }; /* B-tree branch is a node> and links to `size + 1` nodes. */ struct PB_(branch) { struct PB_(node) base, *child[TREE_ORDER]; }; -/** @return Upcasts `as_node` to a branch. */ +/** @return Downcasts `as_node` to a branch. */ static struct PB_(branch) *PB_(branch)(struct PB_(node) *const as_leaf) { return (struct PB_(branch) *)(void *) ((char *)as_leaf - offsetof(struct PB_(branch), base)); } -/** @return Upcasts `as_node` to a branch. */ +/** @return Downcasts `as_node` to a branch. */ static const struct PB_(branch) *PB_(branch_c)(const struct PB_(node) * const as_node) { return (const struct PB_(branch) *)(const void *) ((const char *)as_node - offsetof(struct PB_(branch), base)); } - -/* Subtree is a node with a height. */ -struct PB_(sub) { struct PB_(node) *node; unsigned height; }; /* Address specific entry. */ struct PB_(ref) { struct PB_(node) *node; unsigned height, idx; }; struct PB_(ref_c) { const struct PB_(node) *node; unsigned height, idx; }; +struct PB_(tree) { struct PB_(node) *node; unsigned height; }; +/** To initialize it to an idle state, see tree>, `TRIE_IDLE`, `{0}` + (`C99`), or being `static`. + + ![States.](../doc/states.png) */ +struct B_(tree); +struct B_(tree) { struct PB_(tree) root; }; #ifdef TREE_VALUE /* */ -/** To initialize it to an idle state, see tree>, `TRIE_IDLE`, `{0}` - (`C99`), or being `static`. This is a B-tree, as - . - - ![States.](../doc/states.png) */ -struct B_(tree); -struct B_(tree) { struct PB_(sub) root; }; +/** @return If `ref` in `tree` has a predecessor, then it decrements. */ +static int PB_(to_predecessor)(struct PB_(tree) tree, + struct PB_(ref) *const ref) { + assert(ref); + if(!tree.node || tree.height == UINT_MAX) return 0; /* Empty. */ + if(!ref->node) { /* Null: `ret` is the last key. */ + struct PB_(tree) descend = tree; + while(descend.height) descend.height--, + descend.node = PB_(branch)(descend.node)->child[descend.node->size]; + /* While bulk-loading, could have empty right. */ + if(descend.node->size) ref->node = descend.node, + ref->height = 0, ref->idx = descend.node->size - 1; + else assert(tree.node->size), ref->node = tree.node, + ref->height = tree.height, ref->idx = tree.node->size - 1; + return 1; + } + while(ref->height) ref->height--, + ref->node = PB_(branch_c)(ref->node)->child[ref->idx], + ref->idx = ref->node->size; + if(ref->idx) return ref->idx--, 1; /* Likely. */ +{ /* Re-descend; pick the minimum height node that has a previous key. */ + struct PB_(ref) prev; + unsigned a0; + PB_(key) x; + for(prev.node = 0, x = ref->node->key[0]; tree.height; + tree.node = PB_(branch_c)(tree.node)->child[a0], tree.height--) { + /* fixme: This is repeated. */ + unsigned a1 = tree.node->size; + a0 = 0; + while(a0 < a1) { + const unsigned m = (a0 + a1) / 2; + if(PB_(compare)(x, tree.node->key[m]) > 0) a0 = m + 1; else a1 = m; + } + if(a0) + prev.node = tree.node, prev.height = tree.height, prev.idx = a0 - 1; + } + if(!prev.node) return 0; /* Off the left. */ + *ref = prev; +} return 1; /* Jumped nodes. */ +} +/* @return If `ref_c` in `tree` has a successor, then it increments. */ +#define TREE_TO_SUCCESSOR(to_successor_c, ref_c) \ +static int PB_(to_successor_c)(struct PB_(tree) tree, \ + struct PB_(ref_c) *const ref) { \ + assert(ref); \ + if(!tree.node || tree.height == UINT_MAX) return 0; /* Empty. */ \ + if(!ref->node) \ + ref->node = tree.node, ref->height = tree.height, ref->idx = 0; \ + else \ + ref->idx++; \ + while(ref->height) ref->height--, \ + ref->node = PB_(branch_c)(ref->node)->child[ref->idx], ref->idx = 0; \ + if(ref->idx < ref->node->size) return 1; /* Likely. */ \ + if(!ref->node->size) return 0; /* When bulk-loading. */ \ +{ /* Re-descend; pick the minimum height node that has a next key. */ \ + struct PB_(ref_c) next; \ + unsigned a0; \ + PB_(key) x; \ + for(next.node = 0, x = ref->node->key[ref->node->size - 1]; tree.height; \ + tree.node = PB_(branch_c)(tree.node)->child[a0], tree.height--) { \ + unsigned a1 = tree.node->size; \ + a0 = 0; \ + while(a0 < a1) { \ + const unsigned m = (a0 + a1) / 2; \ + if(PB_(compare)(x, tree.node->key[m]) > 0) a0 = m + 1; else a1 = m;\ + } \ + if(a0 < tree.node->size) \ + next.node = tree.node, next.height = tree.height, next.idx = a0; \ + } \ + if(!next.node) return 0; /* Off the right. */ \ + *ref = next; \ +} return 1; /* Jumped nodes. */ \ +} +TREE_TO_SUCCESSOR(to_successor, ref) +TREE_TO_SUCCESSOR(to_successor_c, ref_c) /* For forward iteration. */ +#undef TREE_TO_SUCCESSOR #define BOX_CONTENT PB_(entry_c) /** Is `e` not null? @implements `is_element_c` */ @@ -236,62 +312,18 @@ static int PB_(is_element_c)(PB_(entry_c) e) { return !!e; #endif } -/* Two copies of the same code, with and without `const`. - @param[sub] A copy of the tree's root. - @param[ref] If it has a null node, starts at the first key; if it's past the - node's limits, uses `sub` to go to the next node. - @return True unless there are no more `ref`. */ -#define TREE_PIN(pin_c, ref_c) \ -static int PB_(pin_c)(struct PB_(sub) sub, struct PB_(ref_c) *const ref) { \ - struct PB_(ref_c) next; \ - unsigned a0; \ - PB_(key) x; \ - assert(ref); \ - if(!sub.node || sub.height == UINT_MAX) return 0; \ - /* Start. */ \ - if(!ref->node) \ - ref->node = sub.node, ref->height = sub.height, ref->idx = 0; \ - /* Descend. */ \ - while(ref->height) ref->height--, \ - ref->node = PB_(branch_c)(ref->node)->child[ref->idx], ref->idx = 0; \ - if(ref->idx < ref->node->size) return 1; /* Likely. */ \ - /* Empty nodes are always at the end, (when bulk loading.) */ \ - if(!ref->node->size) return 0; \ - /* Re-descend tree and note the minimum height node that has a next key. */\ - for(next.node = 0, x = ref->node->key[ref->node->size - 1]; sub.height; \ - sub.node = PB_(branch_c)(sub.node)->child[a0], sub.height--) { \ - unsigned a1 = sub.node->size; a0 = 0; \ - while(a0 < a1) { \ - const unsigned m = (a0 + a1) / 2; \ - if(PB_(compare)(x, sub.node->key[m]) > 0) a0 = m + 1; else a1 = m; \ - } \ - if(a0 < sub.node->size) \ - next.node = sub.node, next.height = sub.height, next.idx = a0; \ - } \ - if(!next.node) return 0; /* Off the right. */ \ - *ref = next; \ - return 1; /* Jumped nodes. */ \ -} -TREE_PIN(pin_c, ref_c) -TREE_PIN(pin, ref) -#undef TREE_PIN -/* This could be expanded! */ - -/* A constant iterator. @implements `forward` */ -struct PB_(forward) { const struct PB_(sub) *root; struct PB_(ref_c) ref; }; -/** @return Before `tree`. @implements `forward_begin` */ -static struct PB_(forward) PB_(forward_begin)(const struct B_(tree) *const - tree) { - struct PB_(forward) it; - it.root = tree ? &tree->root : 0, it.ref.node = 0, - it.ref.height = 0, it.ref.idx = 0; +/* @implements `forward` */ +struct PB_(forward) { const struct PB_(tree) *root; struct PB_(ref_c) next; }; +/** @return Before `tree`. @implements `forward` */ +static struct PB_(forward) PB_(forward)(const struct B_(tree) *const + tree) { struct PB_(forward) it; + it.root = tree ? &tree->root : 0, it.next.node = 0; return it; } -/** Advances `it` to the next element. @return A pointer to the current - element or null. @implements `forward_next` */ -static PB_(entry_c) PB_(forward_next)(struct PB_(forward) *const it) { - return assert(it), PB_(pin_c)(*it->root, &it->ref) ? - PB_(leaf_to_entry_c)(it->ref.node, it->ref.idx++) : PB_(null_entry_c)(); +/** Move to next `it`. @return Element or null. @implements `next_c` */ +static PB_(entry_c) PB_(next_c)(struct PB_(forward) *const it) { + return assert(it), PB_(to_successor_c)(*it->root, &it->next) ? + PB_(leaf_to_entry_c)(it->next.node, it->next.idx) : PB_(null_entry_c)(); } #define BOX_ITERATOR PB_(entry) @@ -303,99 +335,162 @@ static int PB_(is_element)(const PB_(entry) e) { return !!e; #endif } -/* A certain position and the top level tree for backtracking. - @implements `iterator` */ -struct PB_(iterator) { struct PB_(sub) *root; struct PB_(ref) ref; }; -/** @return Before `tree`. @implements `forward_begin` */ -static struct PB_(iterator) PB_(begin)(struct B_(tree) *const tree) { +/* @implements `iterator` */ +struct PB_(iterator) { struct PB_(tree) *root; struct PB_(ref) i; int seen; }; +/** @return Iterator to null in `tree`. @implements `iterator` */ +static struct PB_(iterator) PB_(iterator)(struct B_(tree) *const tree) { struct PB_(iterator) it; - it.root = tree ? &tree->root : 0, it.ref.node = 0, - it.ref.height = 0, it.ref.idx = 0; + it.root = tree ? &tree->root : 0, it.i.node = 0, it.seen = 0; return it; } -/** Advances `it` to the next element. @return A pointer to the current - element or null. @implements `next` */ +/** Advances `it`. @return Element or null. @implements `next` */ static PB_(entry) PB_(next)(struct PB_(iterator) *const it) { - return assert(it), PB_(pin)(*it->root, &it->ref) ? - PB_(leaf_to_entry)(it->ref.node, it->ref.idx++) : PB_(null_entry)(); + assert(it); + if(!it->root + || (it->seen || !it->i.node) && !PB_(to_successor)(*it->root, &it->i)) { + it->i.node = 0, it->seen = 0; + return PB_(null_entry)(); + } + it->seen = 1; + return PB_(leaf_to_entry)(it->i.node, it->i.idx); +} +/** Move to previous `it`. @return Element or null. @implements `previous` */ +static PB_(entry) PB_(previous)(struct PB_(iterator) *const it) { + assert(it && 0); + if(!it->root || !it->i.node && it->seen != -1 + || it->seen && !PB_(to_predecessor)(*it->root, &it->i)) + return PB_(null_entry)(); + it->seen = -1; + return PB_(leaf_to_entry)(it->i.node, it->i.idx); } -//#include "../test/orcish.h" - +/* Want to find slightly different things; code re-use is bad. Confusing. */ +#define TREE_FORTREE(i) i.node = tree->node, i.height = tree->height; ; \ + i.node = PB_(branch_c)(i.node)->child[i.idx], i.height-- +#define TREE_START(i) unsigned hi = i.node->size; i.idx = 0; +#define TREE_FORNODE(i, continue) if(!hi) continue; \ +do { \ + const unsigned m = (i.idx + hi) / 2; \ + if(PB_(compare)(key, i.node->key[m]) > 0) i.idx = m + 1; \ + else hi = m; \ +} while(i.idx < hi); +#define TREE_FLIPPED(i) PB_(compare)(i.node->key[i.idx], key) <= 0 +/** One height at a time. */ static void PB_(find_idx)(struct PB_(ref) *const lo, const PB_(key) key) { - unsigned hi = lo->node->size; - lo->idx = 0; - if(!hi) return; - do { - const unsigned m = (lo->idx + hi) / 2; - if(PB_(compare)(key, lo->node->key[m]) > 0) lo->idx = m + 1; - else hi = m; - } while(lo->idx < hi); + TREE_START((*lo)) + TREE_FORNODE((*lo), return) } - -/** Assume `tree` and `x` are checked for non-empty validity. */ -static struct PB_(ref) PB_(lower_r)(struct PB_(sub) *const tree, - const PB_(key) key, struct PB_(ref) *const unfull, int *const is_equal) { - struct PB_(ref) lo; - for(lo.node = tree->node, lo.height = tree->height; ; - lo.node = PB_(branch_c)(lo.node)->child[lo.idx], lo.height--) { - unsigned hi = lo.node->size; - lo.idx = 0; - if(unfull && hi < TREE_MAX) *unfull = lo; - if(!hi) continue; /* No nodes; bulk-add? */ - do { - const unsigned m = (lo.idx + hi) / 2; - if(PB_(compare)(key, lo.node->key[m]) > 0) lo.idx = m + 1; - else hi = m; - } while(lo.idx < hi); - if(unfull && hi < TREE_MAX) unfull->idx = lo.idx; /* Update. */ - if(!lo.height) break; /* Leaf node. */ - if(lo.idx == lo.node->size) continue; /* Off the end. */ - /* Total order and monotonic, otherwise have to check right. */ - if(PB_(compare)(lo.node->key[lo.idx], key) > 0) continue; - if(is_equal) *is_equal = 1; - break; +/** Finds lower-bound of `key` in `tree`. */ +static struct PB_(ref) PB_(lower_r)(struct PB_(tree) *const tree, + const PB_(key) key) { + struct PB_(ref) i, lo = { 0, 0, 0 }; + for(TREE_FORTREE(i)) { + TREE_START(i) + TREE_FORNODE(i, continue) + if(i.idx < i.node->size) { + lo = i; + /* Might be useful expanding this to multi-keys. */ + if(TREE_FLIPPED(i)) break; + } + if(!i.height) break; } return lo; } +/** Finds lower-bound of `key` in `tree` while counting the non-filled `hole` + and `is_equal`. (fixme: is_equal useless) */ +static struct PB_(ref) PB_(lookup_insert)(struct PB_(tree) *const tree, + const PB_(key) key, struct PB_(ref) *const hole, int *const is_equal) { + struct PB_(ref) lo; + hole->node = 0; + for(TREE_FORTREE(lo)) { + TREE_START(lo) + if(hi < TREE_MAX) *hole = lo; + TREE_FORNODE(lo, continue) + if(lo.node->size < TREE_MAX) hole->idx = lo.idx; + if(lo.idx < lo.node->size && TREE_FLIPPED(lo)) { *is_equal = 1; break; } + if(!lo.height) break; + } + return lo; +} +/** Finds lower-bound of `key` in `tree` while counting the non-minimum `hole` + and `is_equal`. (fixme: is_equal useless) */ +static struct PB_(ref) PB_(lookup_remove)(struct PB_(tree) *const tree, + const PB_(key) key, struct PB_(ref) *const lump) { + struct PB_(ref) lo; + lump->node = 0; + for(TREE_FORTREE(lo)) { + TREE_START(lo) + TREE_FORNODE(lo, continue) + if(lo.node->size > TREE_MIN || lo.height && ( + lo.idx + && PB_(branch)(lo.node)->child[lo.idx - 1]->size > TREE_MIN + || lo.idx < lo.node->size + && PB_(branch)(lo.node)->child[lo.idx + 1]->size > TREE_MIN + )) *lump = lo; + if(lo.idx < lo.node->size && TREE_FLIPPED(lo)) break; + if(!lo.height) { lo.node = 0; break; } /* Was not in. */ + } + if(!lump->node) { + /* Check for root. */ + assert(0); + } + return lo; +} +#undef TREE_FORTREE +#undef TREE_START +#undef TREE_FORNODE +#undef TREE_FLIPPED /** @param[tree] Can be null. @return Lower bound of `x` in `tree`. @order \O(\log |`tree`|) */ -static struct PB_(ref) PB_(lower)(struct PB_(sub) sub, - const PB_(key) x, struct PB_(ref) *const unfull, int *const is_equal) { - if(!sub.node || sub.height == UINT_MAX) { +static struct PB_(ref) PB_(lower)(struct PB_(tree) tree, const PB_(key) x) { + if(!tree.node || tree.height == UINT_MAX) { struct PB_(ref) ref; ref.node = 0; return ref; } else { - return PB_(lower_r)(&sub, x, unfull, is_equal); + return PB_(lower_r)(&tree, x); } } -/** Clears non-empty `tree` and it's children recursively, but doesn't put it - to idle or clear pointers. If `one` is valid, tries to keep one leaf. */ -static void PB_(clear_r)(struct PB_(sub) sub, struct PB_(node) **const one) { - assert(sub.node); - if(!sub.height) { - if(one && !*one) *one = sub.node; - else free(sub.node); +/** Frees non-empty `tree` and it's children recursively, but doesn't put it + to idle or clear pointers. + @param[one] If `one` is valid, tries to keep one leaf. Set to null before. */ +static void PB_(clear_r)(struct PB_(tree) tree, struct PB_(node) **const keep) { + assert(tree.node); + if(!tree.height) { + if(keep && !*keep) *keep = tree.node; + else free(tree.node); } else { - struct PB_(sub) child; + struct PB_(tree) child; unsigned i; - child.height = sub.height - 1; - for(i = 0; i <= sub.node->size; i++) - child.node = PB_(branch)(sub.node)->child[i], - PB_(clear_r)(child, one); - free(PB_(branch)(sub.node)); + child.height = tree.height - 1; + for(i = 0; i <= tree.node->size; i++) + child.node = PB_(branch)(tree.node)->child[i], + PB_(clear_r)(child, keep); + free(PB_(branch)(tree.node)); } } +/** `tree` can be null. */ +static void PB_(clear)(struct B_(tree) *tree) { + struct PB_(node) *one = 0; + /* Already not there/idle/empty. */ + if(!tree || !tree->root.node || tree->root.height == UINT_MAX) return; + PB_(clear_r)(tree->root, &one), assert(one); + /* This is a special state where the tree has one leaf, but it is empty. + This state exists because it gives hysteresis to 0 -- 1 transition. */ + tree->root.node = one; + tree->root.height = UINT_MAX; +} /* Box override information. */ #define BOX_ PB_ #define BOX struct B_(tree) -/** Initializes `tree` to idle. @order \Theta(1) @allow */ -static struct B_(tree) B_(tree)(void) - { struct B_(tree) tree; tree.root.node = 0; tree.root.height = 0; - return tree; } +/** @return Initializes `tree` to idle. @order \Theta(1) @allow */ +static struct B_(tree) B_(tree)(void) { + struct B_(tree) tree; + tree.root.node = 0; tree.root.height = 0; + return tree; +} /** Returns an initialized `tree` to idle, `tree` can be null. @allow */ static void B_(tree_)(struct B_(tree) *const tree) { @@ -403,7 +498,7 @@ static void B_(tree_)(struct B_(tree) *const tree) { if(!tree->root.node) { /* Idle. */ assert(!tree->root.height); } else if(tree->root.height == UINT_MAX) { /* Empty. */ - assert(tree->root.node); free(tree->root.node); + assert(tree->root.node), free(tree->root.node); } else { PB_(clear_r)(tree->root, 0); } @@ -411,12 +506,12 @@ static void B_(tree_)(struct B_(tree) *const tree) { } /** Stores an iteration in a tree. Generally, changes in the topology of the - tree invalidate it. */ + tree invalidate it. (Future: have insert and delete with iterators.) */ struct B_(tree_iterator) { struct PB_(iterator) _; }; /** @return An iterator before the first element of `tree`. Can be null. @allow */ -static struct B_(tree_iterator) B_(tree_begin)(struct B_(tree) *const tree) - { struct B_(tree_iterator) it; it._ = PB_(begin)(tree); return it; } +static struct B_(tree_iterator) B_(tree_iterator)(struct B_(tree) *const tree) + { struct B_(tree_iterator) it; it._ = PB_(iterator)(tree); return it; } /** Advances `it` to the next element. @return A pointer to the current element or null. @allow */ static PB_(entry) B_(tree_next)(struct B_(tree_iterator) *const it) @@ -425,12 +520,13 @@ static PB_(entry) B_(tree_next)(struct B_(tree_iterator) *const it) /** @param[tree] Can be null. @return Finds the smallest entry in `tree` that is at the lower bound of `x`. If `x` is higher than any of `tree`, it will be placed just passed the end. @order \O(\log |`tree`|) @allow */ -static struct B_(tree_iterator) B_(tree_lower)(struct B_(tree) *const tree, - const PB_(key) x) { +static struct B_(tree_iterator) B_(tree_lower_iterator) + (struct B_(tree) *const tree, const PB_(key) x) { struct B_(tree_iterator) it; if(!tree) return it._.root = 0, it; - it._.ref = PB_(lower)(tree->root, x, 0, 0); + it._.i = PB_(lower)(tree->root, x); it._.root = &tree->root; + it._.seen = 0; return it; } @@ -438,23 +534,18 @@ static struct B_(tree_iterator) B_(tree_lower)(struct B_(tree) *const tree, `x = 11 -> null`. @return Lower-bound value match for `x` in `tree` or null if `x` is greater than all in `tree`. @order \O(\log |`tree`|) @allow */ -static PB_(value) *B_(tree_get_next)(struct B_(tree) *const tree, - const PB_(key) x) { - struct PB_(ref) ref; - return tree && (ref = PB_(lower)(tree->root, x, 0, 0), - PB_(pin)(tree->root, &ref)) ? PB_(ref_to_value)(ref) : 0; -} +static PB_(value) *B_(tree_lower_value)(struct B_(tree) *const tree, + const PB_(key) x) + { return tree ? PB_(ref_to_value)(PB_(lower)(tree->root, x)) : 0; } -//#include "../test/orcish.h" -static void PB_(print)(const struct B_(tree) *const tree); -#ifndef TREE_TEST -static void PB_(print)(const struct B_(tree) *const tree) - { (void)tree, printf("not printable\n"); } -#endif +/** Clears `tree`, which can be null, idle, empty, or full. If it is empty or + full, it remains active. */ +static void B_(tree_clear)(struct B_(tree) *const tree) { PB_(clear)(tree); } #ifdef TREE_VALUE /* */ { - struct PB_(node) *leaf = 0, *head = 0, **next = 0, - *sibling; - unsigned new_nodes, i; - struct PB_(ref) add, parent, hole, cursor; - int is_equal; + struct PB_(node) *new_head = 0; + struct PB_(ref) add, hole, cursor; + int is_growing = 0; assert(tree); if(!(add.node = tree->root.node)) goto idle; else if(tree->root.height == UINT_MAX) goto empty; - goto content; + goto descend; idle: /* No reserved memory. */ assert(!add.node && !tree->root.height); if(!(add.node = malloc(sizeof *add.node))) goto catch; @@ -665,83 +766,27 @@ idle: /* No reserved memory. */ goto empty; empty: /* Reserved dynamic memory, but tree is empty. */ assert(add.node && tree->root.height == UINT_MAX); - tree->root.height = 0; + add.height = tree->root.height = 0; add.node->size = 0; add.idx = 0; goto insert; -content: /* Descend the tree; record last node that has space. */ - parent.node = 0, is_equal = 0; - add = PB_(lower_r)(&tree->root, key, &parent, &is_equal); - if(is_equal) goto yield; /* Assumes key is unique. */ - if(parent.node == add.node) goto insert; else goto allocate; -allocate: /* Pre-allocate new nodes in order. */ - new_nodes = parent.node ? parent.height + 1 : tree->root.height + 2; - for(i = 0; i < new_nodes - 1; i++) { - struct PB_(branch) *branch; - if(!(branch = malloc(sizeof *branch))) goto catch; - branch->base.size = 0; - if(!head) head = &branch->base; else *next = &branch->base; - next = branch->child; - } - if(!(leaf = malloc(sizeof *leaf))) goto catch; - leaf->size = 0; - *next = leaf; - hole.node = 0; - if(parent.node) goto split; else goto grow; -grow: /* Raise tree height with zero-size one-child branch. */ - assert(!parent.node && new_nodes); - parent.node = head, head = PB_(branch)(head)->child[0], new_nodes--; - parent.height = ++tree->root.height, parent.idx = 0; - PB_(branch)(parent.node)->child[0] = tree->root.node; - tree->root.node = parent.node; - goto split; -split: /* Simulates bottom-up, overfull node, split; really this is problematic - because we don't have parent pointers and we don't have space for overfull - nodes. So split top-down and leave some nodes blank for filling in next. */ - assert(parent.node && parent.height && parent.node->size < TREE_MAX - && new_nodes); - sibling = head, head = --new_nodes ? PB_(branch)(head)->child[0] : 0; - cursor.node = PB_(branch)(parent.node)->child[parent.idx]; - cursor.height = parent.height - 1; - PB_(find_idx)(&cursor, key); - /* Add one space to unfull parent. This is double-copying when we loop - around a second time in `⎣(TREE_MAX-1)/2⎦/TREE_MAX` cases; not going to - optimize this because it would require a lookahead. */ - memmove(parent.node->key + parent.idx + 1, parent.node->key + parent.idx, - sizeof *parent.node->key * (parent.node->size - parent.idx)); +descend: /* Record last node that has space. */ + { + int is_equal = 0; + add = PB_(lookup_insert)(&tree->root, key, &hole, &is_equal); + if(is_equal) { + /* Assumes key is unique; we might not want this for multi-maps, + but that is not implemented yet. */ #ifdef TREE_VALUE - memmove(parent.node->value + parent.idx + 1, - parent.node->value + parent.idx, - sizeof *parent.node->value * (parent.node->size - parent.idx)); + if(value) *value = PB_(ref_to_value)(add); #endif - assert(0); - /*{ - struct PB_(branch) *const pbranch = PB_(branch)(parent.node); - memmove(<#void *__dst#>, <#const void *__src#>, <#size_t __len#>)... - }*/ - parent.node->size++; - if(cursor.idx == TREE_SPLIT) { /* Down the middle. */ - printf("down middle\n"); - if(!hole.node) hole = parent; /* Maybe? */ - sibling->size = TREE_MAX - TREE_SPLIT - 1; - memcpy(sibling->key, cursor.node->key + TREE_SPLIT, - sizeof *sibling->key * (TREE_MAX - TREE_SPLIT)); -#ifdef TREE_VALUE - memcpy(sibling->value, cursor.node->value + TREE_SPLIT, - sizeof *sibling->value * (TREE_MAX - TREE_SPLIT)); -#endif - if(cursor.height) { - assert(0); + return TREE_YIELD; } - } else if(cursor.idx < TREE_SPLIT) { - assert(0); - } else /* Greater than. */ { - assert(0); } - //found.node = tree->root.node, PB_(find_idx)(&found, key); - assert(0); -insert: /* `add` is referencing an unfull node that we want to insert. */ - assert(add.node && add.idx <= add.node->size && add.node->size < TREE_MAX); + if(hole.node == add.node) goto insert; else goto grow; +insert: /* Leaf has space to spare; usually end up here. */ + assert(add.node && add.idx <= add.node->size && add.node->size < TREE_MAX + && (!add.height || is_growing)); memmove(add.node->key + add.idx + 1, add.node->key + add.idx, sizeof *add.node->key * (add.node->size - add.idx)); #ifdef TREE_VALUE @@ -750,51 +795,491 @@ insert: /* `add` is referencing an unfull node that we want to insert. */ #endif add.node->size++; add.node->key[add.idx] = key; - goto unique; -yield: /* `add` is an existing value. */ -#ifdef TREE_VALUE - if(value) *value = PB_(ref_to_value)(add); -#endif - return TREE_YIELD; -unique: /* `add` is a new value. */ #ifdef TREE_VALUE if(value) *value = PB_(ref_to_value)(add); #endif return TREE_UNIQUE; -catch: - while(head) { - struct PB_(branch) *const branch = PB_(branch)(head); - head = branch->child[0]; +grow: /* Leaf is full. */ { + unsigned new_no = hole.node ? hole.height : tree->root.height + 2; + struct PB_(node) **new_next = &new_head, *new_leaf; + struct PB_(branch) *new_branch; + assert(new_no); + /* Allocate new nodes in succession. */ + while(new_no != 1) { /* All branches except one. */ + if(!(new_branch = malloc(sizeof *new_branch))) goto catch; + new_branch->base.size = 0; + new_branch->child[0] = 0; + *new_next = &new_branch->base, new_next = new_branch->child; + new_no--; } - if(!errno) errno = ERANGE; + /* Last point of potential failure; (don't need to have entry in catch.) */ + if(!(new_leaf = malloc(sizeof *new_leaf))) goto catch; + new_leaf->size = 0; + *new_next = new_leaf; + /* Attach new nodes to the tree. The hole is now an actual hole. */ + if(hole.node) { /* New nodes are a sub-structure of the tree. */ + struct PB_(branch) *holeb = PB_(branch)(hole.node); + memmove(hole.node->key + hole.idx + 1, hole.node->key + hole.idx, + sizeof *hole.node->key * (hole.node->size - hole.idx)); +#ifdef TREE_VALUE + memmove(hole.node->value + hole.idx + 1, hole.node->value + hole.idx, + sizeof *hole.node->value * (hole.node->size - hole.idx)); +#endif + memmove(holeb->child + hole.idx + 2, holeb->child + hole.idx + 1, + sizeof *holeb->child * (hole.node->size - hole.idx)); + holeb->child[hole.idx + 1] = new_head; + hole.node->size++; + } else { /* New nodes raise tree height. */ + struct PB_(branch) *const new_root = PB_(branch)(new_head); + hole.node = new_head, hole.height = ++tree->root.height, hole.idx = 0; + new_head = new_root->child[1] = new_root->child[0]; + new_root->child[0] = tree->root.node, tree->root.node = hole.node; + hole.node->size = 1; + } + cursor = hole; /* Go down; (as opposed to doing it on paper.) */ + goto split; +} split: { /* Split between the new and existing nodes. */ + struct PB_(node) *sibling; + assert(cursor.node && cursor.node->size && cursor.height); + sibling = new_head; + /* Descend now while split hasn't happened -- easier. */ + new_head = --cursor.height ? PB_(branch)(new_head)->child[0] : 0; + cursor.node = PB_(branch)(cursor.node)->child[cursor.idx]; + PB_(find_idx)(&cursor, key); + assert(!sibling->size && cursor.node->size == TREE_MAX); /* Atomic. */ + /* Expand `cursor`, which is full, to multiple nodes. */ + if(cursor.idx < TREE_SPLIT) { /* Descend hole to `cursor`. */ + memcpy(sibling->key, cursor.node->key + TREE_SPLIT, + sizeof *sibling->key * (TREE_MAX - TREE_SPLIT)); +#ifdef TREE_VALUE + memcpy(sibling->value, cursor.node->value + TREE_SPLIT, + sizeof *sibling->value * (TREE_MAX - TREE_SPLIT)); +#endif + hole.node->key[hole.idx] = cursor.node->key[TREE_SPLIT - 1]; +#ifdef TREE_VALUE + hole.node->value[hole.idx] = cursor.node->value[TREE_SPLIT - 1]; +#endif + memmove(cursor.node->key + cursor.idx + 1, + cursor.node->key + cursor.idx, + sizeof *cursor.node->key * (TREE_SPLIT - 1 - cursor.idx)); +#ifdef TREE_VALUE + memmove(cursor.node->value + cursor.idx + 1, + cursor.node->value + cursor.idx, + sizeof *cursor.node->value * (TREE_SPLIT - 1 - cursor.idx)); +#endif + if(cursor.height) { + struct PB_(branch) *const cb = PB_(branch)(cursor.node), + *const sb = PB_(branch)(sibling); + struct PB_(node) *temp = sb->child[0]; + memcpy(sb->child, cb->child + TREE_SPLIT, + sizeof *cb->child * (TREE_MAX - TREE_SPLIT + 1)); + memmove(cb->child + cursor.idx + 2, cb->child + cursor.idx + 1, + sizeof *cb->child * (TREE_SPLIT - 1 - cursor.idx)); + cb->child[cursor.idx + 1] = temp; + } + hole = cursor; + } else if(cursor.idx > TREE_SPLIT) { /* Descend hole to `sibling`. */ + hole.node->key[hole.idx] = cursor.node->key[TREE_SPLIT]; +#ifdef TREE_VALUE + hole.node->value[hole.idx] = cursor.node->value[TREE_SPLIT]; +#endif + hole.node = sibling, hole.height = cursor.height, + hole.idx = cursor.idx - TREE_SPLIT - 1; + memcpy(sibling->key, cursor.node->key + TREE_SPLIT + 1, + sizeof *sibling->key * hole.idx); + memcpy(sibling->key + hole.idx + 1, cursor.node->key + cursor.idx, + sizeof *sibling->key * (TREE_MAX - cursor.idx)); +#ifdef TREE_VALUE + memcpy(sibling->value, cursor.node->value + TREE_SPLIT + 1, + sizeof *sibling->value * hole.idx); + memcpy(sibling->value + hole.idx + 1, cursor.node->value + cursor.idx, + sizeof *sibling->value * (TREE_MAX - cursor.idx)); +#endif + if(cursor.height) { + struct PB_(branch) *const cb = PB_(branch)(cursor.node), + *const sb = PB_(branch)(sibling); + struct PB_(node) *temp = sb->child[0]; + memcpy(sb->child, cb->child + TREE_SPLIT + 1, + sizeof *cb->child * (hole.idx + 1)); + memcpy(sb->child + hole.idx + 2, cb->child + cursor.idx + 1, + sizeof *cb->child * (TREE_MAX - cursor.idx)); + sb->child[hole.idx + 1] = temp; + } + } else { /* Equal split: leave the hole where it is. */ + memcpy(sibling->key, cursor.node->key + TREE_SPLIT, + sizeof *sibling->key * (TREE_MAX - TREE_SPLIT)); +#ifdef TREE_VALUE + memcpy(sibling->value, cursor.node->value + TREE_SPLIT, + sizeof *sibling->value * (TREE_MAX - TREE_SPLIT)); +#endif + if(cursor.height) { + struct PB_(branch) *const cb = PB_(branch)(cursor.node), + *const sb = PB_(branch)(sibling); + memcpy(sb->child + 1, cb->child + TREE_SPLIT + 1, + sizeof *cb->child * (TREE_MAX - TREE_SPLIT)); + } + } + /* Divide `TREE_MAX + 1` into two trees. */ + cursor.node->size = TREE_SPLIT, sibling->size = TREE_MAX - TREE_SPLIT; + if(cursor.height) goto split; /* Loop max `\log_{TREE_MIN} size`. */ + hole.node->key[hole.idx] = key; +#ifdef TREE_VALUE + if(value) *value = PB_(ref_to_value)(hole); +#endif + assert(!new_head); + return TREE_UNIQUE; +} catch: + while(new_head) { + struct PB_(branch) *const top = PB_(branch)(new_head); + new_head = top->child[0]; + free(top); + } + if(!errno) errno = ERANGE; /* Non-POSIX OSs not mandated to set errno. */ return TREE_ERROR; } -#if 0 -/** Updates or adds a pointer to `x` into `trie`. - @param[eject] If not null, on success it will hold the overwritten value or - a pointer-to-null if it did not overwrite any value. - @return Success. @throws[realloc, ERANGE] @order \O(|`key`|) @allow */ -static int B_(trie_put)(struct B_(trie) *const trie, const PB_(entry) x, - PB_(entry) */*const fixme*/eject) - { return assert(trie && x), PB_(put)(trie, x, &eject, 0); } - -/** Adds a pointer to `x` to `trie` only if the entry is absent or if calling - `replace` returns true or is null. - @param[eject] If not null, on success it will hold the overwritten value or - a pointer-to-null if it did not overwrite any value. If a collision occurs and - `replace` does not return true, this will be a pointer to `x`. - @param[replace] Called on collision and only replaces it if the function - returns true. If null, it is semantically equivalent to trie_put>. - @return Success. @throws[realloc, ERANGE] @order \O(|`key`|) @allow */ -static int B_(trie_policy)(struct B_(trie) *const trie, const PB_(entry) x, - PB_(entry) */*const*/ eject, const PB_(replace_fn) replace) - { return assert(trie && x), PB_(put)(trie, x, &eject, replace); } - -/** Tries to remove `key` from `trie`. @return Success. */ -static int B_(trie_remove)(struct B_(trie) *const trie, - const char *const key) { return PB_(remove)(trie, key); } +/** Tries to remove `key` from `tree`. @return Success. */ +static int B_(tree_remove)(struct B_(tree) *const tree, + const PB_(key) key) { + struct PB_(ref) rm, lump; + struct { + struct { + PB_(key) key; +#ifdef TREE_VALUE + PB_(value) value; #endif + struct PB_(node) *link; + int link_lt; + } store[2]; + unsigned next; + } temp; + temp.next = 0; + assert(tree); + /* Traverse down the tree until the `key`. */ + if(!(rm.node = tree->root.node) || tree->root.height == UINT_MAX + || !(rm = PB_(lookup_remove)(&tree->root, key, &lump)).node) return 0; + if(rm.height) goto branch; else goto leaf; +branch: { + struct PB_(ref) succ; + struct PB_(ref) pred; + pred = rm; + succ = rm; + /* This will be more efficient duplicating code? it actually doesn't need + all the code. + while(ref->height) ref->height--, + ref->node = PB_(branch_c)(ref->node)->child[ref->idx], + ref->idx = ref->node->size; + if(ref->idx) return ref->idx--, 1; <-- predecessor + ref->idx++; + while(ref->height) ref->height--, + ref->node = PB_(branch_c)(ref->node)->child[ref->idx], ref->idx = 0; + if(ref->idx < ref->node->size) return 1; <-- successor */ + assert(0); +} leaf: + if(rm.node == lump.node) goto excess; + else if(lump.node) /* and size <= MIN */ goto balance; + else { + assert(0); /* Root is a special case, it can have down to one. */ + goto shrink; + } +balance: { + struct PB_(branch) *const lumpb = PB_(branch)(lump.node); + struct PB_(ref) child; + struct { struct PB_(node) *less, *more; } sibling; + assert(lump.height && lump.idx <= lump.node->size && lump.node->size > 0); + /* Find the child and siblings. */ + child.node = lumpb->child[lump.idx]; + if(child.height = lump.height - 1) PB_(find_idx)(&child, key); + else assert(child.node == rm.node), child.idx = rm.idx; + assert(child.node->size == TREE_MIN); + sibling.less = lump.idx ? lumpb->child[lump.idx - 1] : 0; + sibling.more = lump.idx < lump.node->size ? lumpb->child[lump.idx + 1] : 0; + assert(sibling.less || sibling.more); + /* Pick the sibling with the most nodes to balance. */ + if((sibling.less ? sibling.less->size : 0) + > (sibling.more ? sibling.more->size : 0)) { /* Split left. */ + const unsigned combined = child.node->size + sibling.less->size, + to_promote = combined / 2, to_more = to_promote + 1, + transfer = sibling.less->size - to_more; + assert(sibling.less && lump.idx + && to_promote >= TREE_MIN && to_more <= sibling.less->size); + printf("combined %u; to_promote %u; to_more %u -> transfer %u\n", + combined, to_promote, to_more, transfer); + /* Make way for the keys from the less. */ + printf("move child1 (%u)\n", child.node->size - child.idx - 1); + memmove(child.node->key + child.idx + 1 + transfer, + child.node->key + child.idx + 1, + sizeof *child.node->key * (child.node->size - child.idx - 1)); + printf("move child2 (%u)\n", child.idx); + memmove(child.node->key + 1 + transfer, child.node->key, + sizeof *child.node->key * child.idx); + child.node->key[transfer] = lump.node->key[lump.idx - 1]; + printf("less %u(%u) -> 0\n", to_more, transfer); + memcpy(child.node->key, sibling.less->key + to_more, + sizeof *sibling.less->key * transfer); + lump.node->key[lump.idx - 1] = sibling.less->key[to_promote]; + assert(child.node->size <= TREE_MAX - transfer); + child.node->size += transfer; + sibling.less->size = (unsigned char)to_promote; + if(lump.height > 1) { + struct PB_(branch) *const lessb = PB_(branch)(sibling.less); + assert(0); + } + } else { /* Split right. ***incorrect***? */ + const unsigned combined = child.node->size + sibling.more->size, + to_promote = (combined - 1) / 2, to_more = to_promote - 1; + assert(sibling.more && to_promote && to_promote < sibling.more->size); + printf("to_promote %u, to_less %u\n", to_promote, to_more); + /* Make way for the keys from the less. */ + printf("move child (%u)\n", child.node->size - child.idx - 1); + memmove(child.node->key + child.idx, child.node->key + child.idx + 1, + sizeof *child.node->key * (child.node->size - child.idx - 1)); + child.node->key[child.node->size - 1] = lump.node->key[lump.idx]; + memcpy(child.node->key + child.node->size, sibling.more->key, + sizeof *sibling.more->key * to_more); + assert(0); + } + goto end; +merge: + if(lump.idx < lump.node->size && lumpb->child[lump.idx + 1] && (lump.node->size > TREE_MIN)) { +lean_left: /* Prefer left-leaning: less work for copying. */ + /*left = child.node, right = lumpb->child[lump.idx + 1];*/ + temp.store[temp.next].key = lump.node->key[lump.idx]; +#ifdef TREE_VALUE + temp.store[temp.next].value = lump.node->value[lump.idx]; +#endif + temp.store[temp.next].link = lumpb->child[lump.idx + 1]; + /* Not necessarily! */ + memmove(lump.node->key + lump.idx, lump.node->key + lump.idx + 1, + sizeof *lump.node->key * (lump.node->size - lump.idx - 1)); +#ifdef TREE_VALUE + memmove(lump.node->value + lump.idx, lump.node->value + lump.idx + 1, + sizeof *lump.node->value * (lump.node->size - lump.idx - 1)); +#endif + memmove(lumpb->child + lump.idx + 1, lumpb->child + lump.idx + 2, + sizeof *lumpb->child * (lump.node->size - lump.idx - 1)); + lump.node->size--; + } else { + /*left = lumpb->child[lump.idx - 1], right = child.node;*/ + assert(0); + } + temp.next = !temp.next; + /*printf("remove: merging %s and %s.\n", orcify(left), orcify(right)); + assert(left->size == TREE_MIN && right->size == TREE_MIN);*/ + assert(0); + PB_(find_idx)(&lump, key); + return 0; +} shrink: /* Every node along the path is minimal, the height decreases. */ + assert(0); + return 0; +excess: + assert(rm.node && rm.idx < rm.node->size && rm.node->size > TREE_MIN + && !rm.height); + memmove(rm.node->key + rm.idx, rm.node->key + rm.idx + 1, + sizeof *rm.node->key * (rm.node->size - rm.idx - 1)); +#ifdef TREE_VALUE + memmove(rm.node->value + rm.idx, rm.node->value + rm.idx + 1, + sizeof *rm.node->value * (rm.node->size - rm.idx - 1)); +#endif + rm.node->size--; + goto end; +end: + return 1; +} + + + +/****************************/ + +/* All these are used in clone; it's convenient to use `\O(\log size)` stack + space. [existing branches][new branches][existing leaves][new leaves] no */ +struct PB_(scaffold) { + struct tree_count victim, source; + size_t no; + struct PB_(node) **data; + struct { struct PB_(node) **head, **fresh, **cursor; } branch, leaf; +}; +static int PB_(count_r)(struct PB_(tree) tree, struct tree_count *const no) { + assert(tree.node && tree.height); + if(!++no->branches) return 0; + if(tree.height == 1) { + /* Overflow; aren't guaranteed against this. */ + if(no->leaves + tree.node->size + 1 < no->leaves) return 0; + no->leaves += tree.node->size + 1; + } else { + unsigned char i; + for(i = 0; i <= tree.node->size; i++) { + struct PB_(tree) child; + child.node = PB_(branch)(tree.node)->child[i]; + child.height = tree.height - 1; + if(!PB_(count_r)(child, no)) return 0; + } + } + return 1; +} +static int PB_(count)(const struct B_(tree) *const tree, + struct tree_count *const no) { + assert(tree && no); + no->branches = no->leaves = 0; + if(!tree->root.node) { /* Idle. */ + } else if(tree->root.height == UINT_MAX || !tree->root.height) { + no->leaves = 1; + } else { /* Complex. */ + struct PB_(tree) sub = tree->root; + if(!PB_(count_r)(sub, no)) return 0; + } + return 1; +} +static void PB_(cannibalize_r)(struct PB_(ref) ref, + struct PB_(scaffold) *const sc) { + struct PB_(branch) *branch = PB_(branch)(ref.node); + const int keep_branch = sc->branch.cursor < sc->branch.fresh; + assert(ref.node && ref.height && sc); + if(keep_branch) *sc->branch.cursor = ref.node, sc->branch.cursor++; + if(ref.height == 1) { /* Children are leaves. */ + unsigned n; + for(n = 0; n <= ref.node->size; n++) { + const int keep_leaf = sc->leaf.cursor < sc->leaf.fresh; + struct PB_(node) *child = branch->child[n]; + if(keep_leaf) *sc->leaf.cursor = child, sc->leaf.cursor++; + else free(child); + } + } else while(ref.idx <= ref.node->size) { + struct PB_(ref) child; + child.node = PB_(branch)(ref.node)->child[ref.idx]; + child.height = ref.height - 1; + child.idx = 0; + PB_(cannibalize_r)(child, sc); + ref.idx++; + } + if(!keep_branch) free(branch); +} +static void PB_(cannibalize)(const struct B_(tree) *const tree, + struct PB_(scaffold) *const sc) { + struct PB_(ref) ref; + assert(tree && tree->root.height != UINT_MAX && sc); + /* Nothing to cannibalize. */ + if(!sc->victim.branches && !sc->victim.leaves) return; + assert(tree->root.node); + ref.node = tree->root.node, ref.height = tree->root.height, ref.idx = 0; + sc->branch.cursor = sc->branch.head; + sc->leaf.cursor = sc->leaf.head; + if(ref.height) { + PB_(cannibalize_r)(ref, sc); + } else { /* Just one leaf. */ + *sc->leaf.cursor = ref.node; + } +} +static struct PB_(node) *PB_(clone_r)(struct PB_(tree) src, + struct PB_(scaffold) *const sc) { + struct PB_(node) *node; + if(src.height) { + struct PB_(branch) *const cpyb = PB_(branch)(src.node), + *const branch = PB_(branch)(node = *sc->branch.cursor++); + unsigned i; + *node = *src.node; /* Copy node. */ + src.height--; + for(i = 0; i <= src.node->size; i++) { /* Different links. */ + src.node = cpyb->child[i]; + branch->child[i] = PB_(clone_r)(src, sc); + } + } else { /* Leaves. */ + node = *sc->leaf.cursor++; + *node = *src.node; + } + return node; +} +static struct PB_(tree) PB_(clone)(const struct PB_(tree) *const src, + struct PB_(scaffold) *const sc) { + struct PB_(tree) sub; + assert(src && src->node && sc); + /* Go back to the beginning of the scaffold and pick off one by one. */ + sc->branch.cursor = sc->branch.head; + sc->leaf.cursor = sc->leaf.head; + sub.node = PB_(clone_r)(*src, sc); + sub.height = src->height; + /* Used up all of them. No concurrent modifications, please. */ + assert(sc->branch.cursor == sc->leaf.head + && sc->leaf.cursor == sc->data + sc->no); + return sub; +} +/** `source` is copied to, and overwrites, `tree`. + @param[source] In the case where it's null or idle, if `tree` is empty, then + it continues to be. + @return Success, otherwise `tree` is not modified. + @throws[malloc] @throws[EDOM] `tree` is null. @throws[ERANGE] The size of + `source` doesn't fit into `size_t`. @allow */ +static int B_(tree_clone)(struct B_(tree) *const tree, + const struct B_(tree) *const source) { + struct PB_(scaffold) sc; + int success = 1; + sc.data = 0; /* Need to keep this updated to catch. */ + if(!tree) { errno = EDOM; goto catch; } + /* Count the number of nodes and set up to copy. */ + if(!PB_(count)(tree, &sc.victim) || !PB_(count)(source, &sc.source) + || (sc.no = sc.source.branches + sc.source.leaves) < sc.source.branches) + { errno = ERANGE; goto catch; } /* Overflow. */ + printf("tree_clone: victim.branches %zu; victim.leaves %zu; " + "source.branches %zu; source.leaves %zu.\n", sc.victim.branches, + sc.victim.leaves, sc.source.branches, sc.source.leaves); + if(!sc.no) { PB_(clear)(tree); goto finally; } /* No need to allocate. */ + if(!(sc.data = malloc(sizeof *sc.data * sc.no))) + { if(!errno) errno = ERANGE; goto catch; } + /* debug */ + { + size_t i; + for(i = 0; i < sc.no; i++) sc.data[i] = 0; + } + { /* Ready scaffold. */ + struct tree_count need; + need.leaves = sc.source.leaves > sc.victim.leaves + ? sc.source.leaves - sc.victim.leaves : 0; + need.branches = sc.source.branches > sc.victim.branches + ? sc.source.branches - sc.victim.branches : 0; + sc.branch.head = sc.data; + sc.branch.fresh = sc.branch.cursor + = sc.branch.head + sc.source.branches - need.branches; + sc.leaf.head = sc.branch.fresh + need.branches; + sc.leaf.fresh = sc.leaf.cursor + = sc.leaf.head + sc.source.leaves - need.leaves; + assert(sc.leaf.fresh + need.leaves == sc.data + sc.no); + } + /* Add new nodes. */ + while(sc.branch.cursor != sc.leaf.head) { + struct PB_(branch) *branch; + if(!(branch = malloc(sizeof *branch))) goto catch; + branch->base.size = 0; + branch->child[0] = 0; + *sc.branch.cursor++ = &branch->base; + } + while(sc.leaf.cursor != sc.data + sc.no) { + struct PB_(node) *leaf; + if(!(leaf = malloc(sizeof *leaf))) goto catch; + leaf->size = 0; + *sc.leaf.cursor++ = leaf; + } + /* Resources acquired; now we don't care about tree. */ + PB_(cannibalize)(tree, &sc); + /* The scaffold has the exact number of nodes we need. Overwrite. */ + tree->root = PB_(clone)(&source->root, &sc); + goto finally; +catch: + success = 0; + if(!sc.data) goto finally; + while(sc.leaf.cursor != sc.leaf.fresh) { + struct PB_(node) *leaf = *(--sc.leaf.cursor); + assert(leaf); + free(leaf); + } + while(sc.branch.cursor != sc.branch.fresh) { + struct PB_(branch) *branch = PB_(branch)(*(--sc.branch.cursor)); + assert(branch); + free(branch); + } +finally: + free(sc.data); /* Temporary memory. */ + return success; +} #ifdef TREE_TEST /*