Removed the directory now that it's duplicated in interpet.
This commit is contained in:
parent
8a3718b840
commit
b73194d47d
@ -1,242 +0,0 @@
|
||||
# GNU Make 3.81; MacOSX gcc 4.2.1; clang 19.6.0; MacOSX MinGW 4.3.0
|
||||
|
||||
# https://stackoverflow.com/questions/18136918/how-to-get-current-relative-directory-of-your-makefile
|
||||
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
||||
current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path))))
|
||||
|
||||
project := $(current_dir)
|
||||
|
||||
# dirs
|
||||
src := src
|
||||
test := test
|
||||
build := build
|
||||
bin := bin
|
||||
backup := backup
|
||||
doc := doc
|
||||
media := media
|
||||
#lemon := lemon
|
||||
PREFIX := /usr/local
|
||||
|
||||
# files in $(bin)
|
||||
install := $(project)-`date +%Y-%m-%d`
|
||||
|
||||
# extra stuff we should back up
|
||||
extra :=
|
||||
|
||||
# John Graham-Cumming: rwildcard is a recursive wildcard
|
||||
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) \
|
||||
$(filter $(subst *,%,$2),$d))
|
||||
|
||||
java_srcs := $(call rwildcard, $(src), *.java)
|
||||
all_c_srcs := $(call rwildcard, $(src), *.c)
|
||||
c_re_srcs := $(call rwildcard, $(src), *.re.c)
|
||||
c_rec_srcs := $(call rwildcard, $(src), *.re_c.c)
|
||||
c_gperf_srcs := $(call rwildcard, $(src), *.gperf.c)
|
||||
c_srcs := $(filter-out $(c_re_srcs) $(c_rec_srcs) $(c_gperf_srcs), $(all_c_srcs))
|
||||
h_srcs := $(call rwildcard, $(src), *.h)
|
||||
y_srcs := $(call rwildcard, $(src), *.y)
|
||||
all_c_tests := $(call rwildcard, $(test), *.c)
|
||||
c_re_tests := $(call rwildcard, $(test), *.re.c)
|
||||
c_rec_tests := $(call rwildcard, $(test), *.re_c.c)
|
||||
c_tests := $(filter-out $(c_re_tests) $(c_rec_tests), $(all_c_tests))
|
||||
h_tests := $(call rwildcard, $(test), *.h)
|
||||
icons := $(call rwildcard, $(media), *.ico)
|
||||
|
||||
# combinations
|
||||
all_h := $(h_srcs) $(h_tests)
|
||||
all_srcs := $(java_srcs) $(all_c_srcs) $(y_srcs)
|
||||
all_tests := $(all_c_tests)
|
||||
all_icons := $(icons)
|
||||
|
||||
java_class := $(patsubst $(src)/%.java, $(build)/%.class, $(java_srcs))
|
||||
c_objs := $(patsubst $(src)/%.c, $(build)/%.o, $(c_srcs))
|
||||
# must not conflict, eg, foo.c.re and foo.c would go to the same thing
|
||||
c_re_builds := $(patsubst $(src)/%.re.c, $(build)/%.c, $(c_re_srcs))
|
||||
c_re_test_builds := $(patsubst $(test)/%.re.c, $(build)/$(test)/%.c, $(c_re_tests))
|
||||
c_rec_builds := $(patsubst $(src)/%.re_c.c, $(build)/%.c, $(c_rec_srcs))
|
||||
c_rec_test_builds := $(patsubst $(test)/%.re_c.c, $(build)/%.c, $(c_rec_tests))
|
||||
c_y_builds := $(patsubst $(src)/%.y, $(build)/%.c, $(y_srcs))
|
||||
c_gperf_builds := $(patsubst $(src)/%.gperf.c, $(build)/%.c, $(c_gperf_srcs))
|
||||
# together .re/.re_c/.y/.gperf.c
|
||||
c_other_objs := $(patsubst $(build)/%.c, $(build)/%.o, $(c_re_builds) \
|
||||
$(c_rec_builds) $(c_re_test_builds) $(c_rec_test_builds) $(c_y_builds) $(c_gperf_builds))
|
||||
test_c_objs := $(patsubst $(test)/%.c, $(build)/$(test)/%.o, $(c_tests))
|
||||
html_docs := $(patsubst $(src)/%.c, $(doc)/%.html, $(c_srcs))
|
||||
|
||||
cdoc := cdoc
|
||||
re2c := re2c
|
||||
mkdir := mkdir -p
|
||||
cat := cat
|
||||
zip := zip
|
||||
bison := bison
|
||||
#lemon := lemon
|
||||
gperf := gperf
|
||||
|
||||
target := # -mwindows
|
||||
optimize := -ffast-math
|
||||
warnbasic := -Wall -pedantic #-ansi # -std=c99
|
||||
# Some stuff is really new.
|
||||
warnclang := -Wextra \
|
||||
-Weverything \
|
||||
-Wno-comma \
|
||||
-Wno-logical-op-parentheses \
|
||||
-Wno-parentheses \
|
||||
-Wno-documentation-unknown-command \
|
||||
-Wno-documentation \
|
||||
-Wno-shift-op-parentheses \
|
||||
-Wno-empty-body \
|
||||
-Wno-padded \
|
||||
-Wno-switch-enum \
|
||||
-Wno-missing-noreturn \
|
||||
-Wno-implicit-fallthrough
|
||||
|
||||
# https://stackoverflow.com/a/12099167
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
warnclang += -Wno-poison-system-directories
|
||||
endif
|
||||
|
||||
warn := $(warnbasic) $(warnclang)
|
||||
|
||||
CC := clang # gcc
|
||||
CF := $(target) $(optimize) $(warn)
|
||||
OF := # -lm -framework OpenGL -framework GLUT or -lglut -lGLEW
|
||||
|
||||
# Jakob Borg and Eldar Abusalimov
|
||||
# $(ARGS) is all the extra arguments; $(BRGS) is_all_the_extra_arguments
|
||||
EMPTY :=
|
||||
SPACE := $(EMPTY) $(EMPTY)
|
||||
ifeq (backup, $(firstword $(MAKECMDGOALS)))
|
||||
ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
|
||||
BRGS := $(subst $(SPACE),_,$(ARGS))
|
||||
ifneq (,$(BRGS))
|
||||
BRGS := -$(BRGS)
|
||||
endif
|
||||
$(eval $(ARGS):;@:)
|
||||
endif
|
||||
ifeq (release, $(firstword $(MAKECMDGOALS)))
|
||||
CF += -funroll-loops -Ofast -D NDEBUG # -O3
|
||||
OF += -Ofast
|
||||
else
|
||||
CF += -g
|
||||
endif
|
||||
|
||||
######
|
||||
# compiles the programme by default
|
||||
|
||||
default: $(bin)/$(project)
|
||||
# . . . success; executable is in $(bin)/$(project)
|
||||
|
||||
docs: $(html_docs)
|
||||
|
||||
# linking
|
||||
$(bin)/$(project): $(c_objs) $(c_other_objs) $(test_c_objs)
|
||||
# linking rule
|
||||
@$(mkdir) $(bin)
|
||||
$(CC) $(OF) -o $@ $^
|
||||
|
||||
# compiling
|
||||
#$(lemon)/$(bin)/$(lem): $(lemon)/$(src)/lemon.c
|
||||
# # compiling lemon
|
||||
# @$(mkdir) $(lemon)/$(bin)
|
||||
# $(CC) $(CF) -o $@ $<
|
||||
|
||||
$(c_objs): $(build)/%.o: $(src)/%.c $(all_h)
|
||||
# c_objs rule
|
||||
@$(mkdir) $(build)
|
||||
$(CC) $(CF) -c -o $@ $<
|
||||
|
||||
$(c_other_objs): $(build)/%.o: $(build)/%.c $(all_h)
|
||||
# c_other_objs rule
|
||||
$(CC) $(CF) -c -o $@ $<
|
||||
|
||||
$(test_c_objs): $(build)/$(test)/%.o: $(test)/%.c $(all_h)
|
||||
# test_c_objs rule
|
||||
@$(mkdir) $(build)
|
||||
@$(mkdir) $(build)/$(test)
|
||||
$(CC) $(CF) -c -o $@ $<
|
||||
|
||||
# -8 made my file 32767 lines or longer
|
||||
|
||||
$(c_re_builds): $(build)/%.c: $(src)/%.re.c
|
||||
# *.re.c build rule
|
||||
@$(mkdir) $(build)
|
||||
$(re2c) -W -T -o $@ $<
|
||||
|
||||
$(c_re_test_builds): $(build)/$(test)/%.c: $(test)/%.re.c
|
||||
# *.re.c tests rule
|
||||
@$(mkdir) $(build)
|
||||
@$(mkdir) $(build)/$(test)
|
||||
$(re2c) -W -T -o $@ $<
|
||||
|
||||
$(c_rec_builds): $(build)/%.c: $(src)/%.re_c.c
|
||||
# *.re_c.c (conditions) build rule
|
||||
@$(mkdir) $(build)
|
||||
$(re2c) -W -T -c -o $@ $<
|
||||
|
||||
$(c_rec_test_builds): $(build)/$(test)/%.c: $(test)/%.re_c.c
|
||||
# *.re_c.c (conditions) tests rule
|
||||
@$(mkdir) $(build)
|
||||
@$(mkdir) $(build)/$(test)
|
||||
$(re2c) -W -T -c -o $@ $<
|
||||
|
||||
$(c_y_builds): $(build)/%.c: $(src)/%.y # $(lemon)/$(bin)/$(lem)
|
||||
# .y rule
|
||||
@$(mkdir) $(build)
|
||||
$(bison) -o $@ $<
|
||||
|
||||
$(c_gperf_builds): $(build)/%.c: $(src)/%.gperf.c
|
||||
# *.gperf.c build rule
|
||||
@$(mkdir) $(build)
|
||||
$(gperf) $@ --output-file $<
|
||||
|
||||
$(html_docs): $(doc)/%.html: $(src)/%.c $(src)/%.h
|
||||
# docs rule
|
||||
@$(mkdir) $(doc)
|
||||
cat $^ | $(cdoc) > $@
|
||||
|
||||
######
|
||||
# phoney targets
|
||||
|
||||
.PHONY: setup clean backup icon install uninstall test docs release
|
||||
|
||||
clean:
|
||||
-rm -f $(c_objs) $(test_c_objs) $(c_other_objs) $(c_re_builds) \
|
||||
$(c_rec_builds) $(html_docs)
|
||||
-rm -rf $(bin)/$(test)
|
||||
|
||||
backup:
|
||||
@$(mkdir) $(backup)
|
||||
$(zip) $(backup)/$(project)-`date +%Y-%m-%dT%H%M%S`$(BRGS).zip \
|
||||
readme.txt Makefile $(all_h) $(all_srcs) $(all_tests) $(all_icons)
|
||||
|
||||
icon: default
|
||||
# . . . setting icon on a Mac.
|
||||
cp $(media)/$(icon) $(bin)/$(icon)
|
||||
-sips --addIcon $(bin)/$(icon)
|
||||
-DeRez -only icns $(bin)/$(icon) > $(bin)/$(RSRC)
|
||||
-Rez -append $(bin)/$(RSRC) -o $(bin)/$(project)
|
||||
-SetFile -a C $(bin)/$(project)
|
||||
|
||||
setup: default icon
|
||||
@$(mkdir) $(bin)/$(install)
|
||||
cp $(bin)/$(project) readme.txt $(bin)/$(install)
|
||||
rm -f $(bin)/$(install)-MacOSX.dmg
|
||||
# or rm -f $(BDIR)/$(INST)-Win32.zip
|
||||
hdiutil create $(bin)/$(install)-MacOSX.dmg -volname "$(project)" -srcfolder $(bin)/$(install)
|
||||
# or zip $(BDIR)/$(INST)-Win32.zip -r $(BDIR)/$(INST)
|
||||
rm -R $(bin)/$(install)
|
||||
|
||||
# this needs work
|
||||
release: clean default
|
||||
strip $(bin)/$(project)
|
||||
# define NDEBUG
|
||||
|
||||
install: release
|
||||
@$(mkdir) -p $(DESTDIR)$(PREFIX)/bin
|
||||
cp $(bin)/$(project) $(DESTDIR)$(PREFIX)/bin/$(project)
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/$(project)
|
||||
|
||||
docs: $(html_docs)
|
@ -1,430 +0,0 @@
|
||||
/** @license 2016 Neil Edelman, distributed under the terms of the
|
||||
[MIT License](https://opensource.org/licenses/MIT).
|
||||
|
||||
@abstract Stand-alone header <src/array.h>; examples <test/test_array.c>; on a
|
||||
compatible workstation, `make` creates the test suite of the examples.
|
||||
|
||||
@subtitle Contiguous dynamic array
|
||||
|
||||
![Example of array.](../doc/array.png)
|
||||
|
||||
<tag:<A>array> is a dynamic array that stores contiguous <typedef:<PA>type>.
|
||||
Resizing may be necessary when increasing the size of the array; this incurs
|
||||
amortised cost, and any pointers to this memory may become stale.
|
||||
|
||||
@param[ARRAY_NAME, ARRAY_TYPE]
|
||||
`<A>` that satisfies `C` naming conventions when mangled and a valid tag-type,
|
||||
<typedef:<PA>type>, associated therewith; required. `<PA>` is private, whose
|
||||
names are prefixed in a manner to avoid collisions.
|
||||
|
||||
@param[ARRAY_COMPARE, ARRAY_IS_EQUAL]
|
||||
Compare `<CMP>` trait contained in <src/compare.h>. Requires
|
||||
`<name>[<trait>]compare` to be declared as <typedef:<PCMP>compare_fn> or
|
||||
`<name>[<trait>]is_equal` to be declared as <typedef:<PCMP>bipredicate_fn>,
|
||||
respectfully, (but not both.)
|
||||
|
||||
@param[ARRAY_TO_STRING]
|
||||
To string `<STR>` trait contained in <src/to_string.h>. Requires
|
||||
`<name>[<trait>]to_string` be declared as <typedef:<PSTR>to_string_fn>.
|
||||
|
||||
@param[ARRAY_EXPECT_TRAIT, ARRAY_TRAIT]
|
||||
Named traits are obtained by including `array.h` multiple times with
|
||||
`ARRAY_EXPECT_TRAIT` and then subsequently including the name in
|
||||
`ARRAY_TRAIT`.
|
||||
|
||||
@std C89 */
|
||||
|
||||
#if !defined(ARRAY_NAME) || !defined(ARRAY_TYPE)
|
||||
#error Name or tag type undefined.
|
||||
#endif
|
||||
#if defined(ARRAY_TRAIT) ^ defined(BOX_TYPE)
|
||||
#error ARRAY_TRAIT name must come after ARRAY_EXPECT_TRAIT.
|
||||
#endif
|
||||
#if defined(ARRAY_COMPARE) && defined(ARRAY_IS_EQUAL)
|
||||
#error Only one can be defined at a time.
|
||||
#endif
|
||||
#if defined(ARRAY_TEST) && (!defined(ARRAY_TRAIT) && !defined(ARRAY_TO_STRING) \
|
||||
|| defined(ARRAY_TRAIT) && !defined(ARRAY_HAS_TO_STRING))
|
||||
#error Test requires to string.
|
||||
#endif
|
||||
|
||||
#ifndef ARRAY_H /* <!-- idempotent */
|
||||
#define ARRAY_H
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#if defined(ARRAY_CAT_) || defined(ARRAY_CAT) || defined(A_) || defined(PA_)
|
||||
#error Unexpected defines.
|
||||
#endif
|
||||
/* <Kernighan and Ritchie, 1988, p. 231>. */
|
||||
#define ARRAY_CAT_(n, m) n ## _ ## m
|
||||
#define ARRAY_CAT(n, m) ARRAY_CAT_(n, m)
|
||||
#define A_(n) ARRAY_CAT(ARRAY_NAME, n)
|
||||
#define PA_(n) ARRAY_CAT(array, A_(n))
|
||||
#endif /* idempotent --> */
|
||||
|
||||
#if !defined(restrict) && (!defined(__STDC__) || !defined(__STDC_VERSION__) \
|
||||
|| __STDC_VERSION__ < 199901L)
|
||||
#define ARRAY_RESTRICT /* Undo this at the end. */
|
||||
#define restrict /* Attribute only in C99+. */
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ARRAY_TRAIT /* <!-- base code */
|
||||
|
||||
#ifndef ARRAY_MIN_CAPACITY /* <!-- !min; */
|
||||
#define ARRAY_MIN_CAPACITY 3 /* > 1 */
|
||||
#endif /* !min --> */
|
||||
|
||||
/** A valid tag type set by `ARRAY_TYPE`. */
|
||||
typedef ARRAY_TYPE PA_(type);
|
||||
|
||||
/** Manages the array field `data` which has `size` elements. The space is
|
||||
indexed up to `capacity`, which is at least `size`. The fields should be
|
||||
treated as read-only; any modification is liable to cause the array to go into
|
||||
an invalid state.
|
||||
|
||||
![States.](../doc/states.png) */
|
||||
struct A_(array) { PA_(type) *data; size_t size, capacity; };
|
||||
/* !data -> !size, data -> capacity >= min && size <= capacity <= max */
|
||||
|
||||
/** Returns null. */
|
||||
static PA_(type) *PA_(null)(void) { return 0; }
|
||||
/** Is `x` not null? @implements `is_element` */
|
||||
static int PA_(is_element)(const PA_(type) *const x) { return !!x; }
|
||||
/* @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 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 || (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 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. Unused.)
|
||||
@return There was an element. @order \O(`a.size`). @implements `remove` */
|
||||
static int PA_(remove)(struct PA_(iterator) *const it) {
|
||||
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;
|
||||
}
|
||||
/** @return Iterator at element `idx` of `a`.
|
||||
@implements `iterator_at` */
|
||||
static struct PA_(iterator) PA_(iterator_at)(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` */
|
||||
static PA_(type) *PA_(at)(const struct A_(array) *a, const size_t idx)
|
||||
{ return a->data + idx; }
|
||||
/** Writes `size` to `a`. @implements `tell_size` */
|
||||
static void PA_(tell_size)(struct A_(array) *a, const size_t size)
|
||||
{ assert(a); a->size = size; }
|
||||
|
||||
/** May become invalid after a topological change to any items previous. */
|
||||
struct A_(array_iterator);
|
||||
struct A_(array_iterator) { struct PA_(iterator) _; };
|
||||
|
||||
/** Zeroed data (not all-bits-zero) is initialized.
|
||||
@return An idle array. @order \Theta(1) @allow */
|
||||
static struct A_(array) A_(array)(void)
|
||||
{ struct A_(array) a; a.data = 0, a.capacity = a.size = 0; return a; }
|
||||
|
||||
/** If `a` is not null, destroys and returns it to idle. @allow */
|
||||
static void A_(array_)(struct A_(array) *const a)
|
||||
{ if(a) free(a->data), *a = A_(array)(); }
|
||||
|
||||
/** @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_iterator_at)(struct A_(array) *a,
|
||||
size_t idx) { struct A_(array_iterator) it;
|
||||
it._ = PA_(iterator_at)(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->_); }
|
||||
/** @return `it` previous element. */
|
||||
static PA_(type) *A_(array_previous)(struct A_(array_iterator) *const it)
|
||||
{ return assert(it), PA_(previous)(&it->_); }
|
||||
|
||||
/** Ensures `min` capacity of `a`. Invalidates pointers in `a`. @param[min] If
|
||||
zero, does nothing. @return Success; otherwise, `errno` will be set.
|
||||
@throws[ERANGE] Tried allocating more then can fit in `size_t` or `realloc`
|
||||
doesn't follow POSIX. @throws[realloc] @allow */
|
||||
static int A_(array_reserve)(struct A_(array) *const a, const size_t min) {
|
||||
size_t c0;
|
||||
PA_(type) *data;
|
||||
const size_t max_size = (size_t)~0 / sizeof *a->data;
|
||||
assert(a);
|
||||
if(a->data) {
|
||||
assert(a->size <= a->capacity);
|
||||
if(min <= a->capacity) return 1;
|
||||
c0 = a->capacity < ARRAY_MIN_CAPACITY
|
||||
? ARRAY_MIN_CAPACITY : a->capacity;
|
||||
} else { /* Idle. */
|
||||
assert(!a->size && !a->capacity);
|
||||
if(!min) return 1;
|
||||
c0 = ARRAY_MIN_CAPACITY;
|
||||
}
|
||||
if(min > max_size) return errno = ERANGE, 0;
|
||||
/* `c_n = a1.625^n`, approximation golden ratio `\phi ~ 1.618`. */
|
||||
while(c0 < min) { /* \O(\log min), in practice, negligible. */
|
||||
size_t c1 = c0 + (c0 >> 1) + (c0 >> 3);
|
||||
if(c0 >= c1) { c0 = max_size; break; } /* Unlikely. */
|
||||
c0 = c1;
|
||||
}
|
||||
if(!(data = realloc(a->data, sizeof *a->data * c0)))
|
||||
{ if(!errno) errno = ERANGE; return 0; }
|
||||
a->data = data, a->capacity = c0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** The capacity of `a` will be increased to at least `n` elements beyond the
|
||||
size. Invalidates any pointers in `a`.
|
||||
@return The start of the buffered space at the back of the array. If `a` is
|
||||
idle and `buffer` is zero, a null pointer is returned, otherwise null
|
||||
indicates an error. @throws[realloc] @allow */
|
||||
static PA_(type) *A_(array_buffer)(struct A_(array) *const a, const size_t n) {
|
||||
assert(a);
|
||||
if(a->size > (size_t)~0 - n) { errno = ERANGE; return 0; }
|
||||
return A_(array_reserve)(a, a->size + n) && a->data ? a->data + a->size : 0;
|
||||
}
|
||||
|
||||
/** Appends `n` contiguous items on the back of `a`.
|
||||
@implements `append` from `BOX_CONTIGUOUS` */
|
||||
static PA_(type) *PA_(append)(struct A_(array) *const a, const size_t n) {
|
||||
PA_(type) *b;
|
||||
if(!(b = A_(array_buffer)(a, n))) return 0;
|
||||
assert(n <= a->capacity && a->size <= a->capacity - n);
|
||||
return a->size += n, b;
|
||||
}
|
||||
|
||||
/** Adds `n` un-initialised elements at position `at` in `a`. It will
|
||||
invalidate any pointers in `a` if the buffer holds too few elements.
|
||||
@param[at] A number smaller than or equal to `a.size`; if `a.size`, this
|
||||
function behaves as <fn:<A>array_append>.
|
||||
@return A pointer to the start of the new region, where there are `n`
|
||||
elements. @throws[realloc, ERANGE] @allow */
|
||||
static PA_(type) *A_(array_insert)(struct A_(array) *const a,
|
||||
const size_t n, const size_t at) {
|
||||
/* Investigate `n` is better than `element`; all the other are element. But
|
||||
also, when would I ever use this? */
|
||||
const size_t old_size = a->size;
|
||||
PA_(type) *const b = PA_(append)(a, n);
|
||||
assert(a && at <= old_size);
|
||||
if(!b) return 0;
|
||||
memmove(a->data + at + n, a->data + at, sizeof *a->data * (old_size - at));
|
||||
return a->data + at;
|
||||
}
|
||||
|
||||
/** @return Adds (push back) one new element of `a`. The buffer space holds at
|
||||
least one element, or it may invalidate pointers in `a`.
|
||||
@order amortised \O(1) @throws[realloc, ERANGE] @allow */
|
||||
static PA_(type) *A_(array_new)(struct A_(array) *const a)
|
||||
{ return PA_(append)(a, 1); }
|
||||
|
||||
/** Shrinks the capacity `a` to the size, freeing unused memory. If the size is
|
||||
zero, it will be in an idle state. Invalidates pointers in `a`.
|
||||
@return Success. @throws[ERANGE, realloc] (Unlikely) `realloc` error. */
|
||||
static int A_(array_shrink)(struct A_(array) *const a) {
|
||||
PA_(type) *data;
|
||||
size_t c;
|
||||
assert(a && a->capacity >= a->size);
|
||||
if(!a->data) return assert(!a->size && !a->capacity), 1;
|
||||
c = a->size && a->size > ARRAY_MIN_CAPACITY ? a->size : ARRAY_MIN_CAPACITY;
|
||||
if(!(data = realloc(a->data, sizeof *a->data * c)))
|
||||
{ if(!errno) errno = ERANGE; return 0; }
|
||||
a->data = data, a->capacity = c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Removes `element` from `a`. Do not attempt to remove an element that is not
|
||||
in `a`. @order \O(`a.size`). @allow */
|
||||
static void A_(array_remove)(struct A_(array) *const a,
|
||||
PA_(type) *const element) {
|
||||
const size_t n = (size_t)(element - a->data);
|
||||
assert(a && element && element >= a->data && element < a->data + a->size);
|
||||
memmove(element, element + 1, sizeof *element * (--a->size - n));
|
||||
}
|
||||
|
||||
/** Removes `datum` from `a` and replaces it with the tail. Do not attempt to
|
||||
remove an element that is not in `a`. @order \O(1). @allow */
|
||||
static void A_(array_lazy_remove)(struct A_(array) *const a,
|
||||
PA_(type) *const datum) {
|
||||
size_t n = (size_t)(datum - a->data);
|
||||
assert(a && datum && datum >= a->data && datum < a->data + a->size);
|
||||
if(--a->size != n) memcpy(datum, a->data + a->size, sizeof *datum);
|
||||
}
|
||||
|
||||
/** Sets `a` to be empty. That is, the size of `a` will be zero, but if it was
|
||||
previously in an active non-idle state, it continues to be.
|
||||
@order \Theta(1) @allow */
|
||||
static void A_(array_clear)(struct A_(array) *const a)
|
||||
{ assert(a), a->size = 0; }
|
||||
|
||||
/** @return The last element or null if `a` is empty. @order \Theta(1) @allow */
|
||||
static PA_(type) *A_(array_peek)(const struct A_(array) *const a)
|
||||
{ return assert(a), a->size ? a->data + a->size - 1 : 0; }
|
||||
|
||||
/** @return Value from the the top of `a` that is removed or null if the array
|
||||
is empty. @order \Theta(1) @allow */
|
||||
static PA_(type) *A_(array_pop)(struct A_(array) *const a)
|
||||
{ return assert(a), a->size ? a->data + --a->size : 0; }
|
||||
|
||||
/** Adds `n` elements to the back of `a`. It will invalidate pointers in `a` if
|
||||
`n` is greater than the buffer space.
|
||||
@return A pointer to the elements. If `a` is idle and `n` is zero, a null
|
||||
pointer will be returned, otherwise null indicates an error.
|
||||
@throws[realloc, ERANGE] @allow */
|
||||
static PA_(type) *A_(array_append)(struct A_(array) *const a, const size_t n)
|
||||
{ return assert(a), PA_(append)(a, n); }
|
||||
|
||||
/** Indices [`i0`, `i1`) of `a` will be replaced with a copy of `b`.
|
||||
@param[b] Can be null, which acts as empty, but cannot overlap with `a`.
|
||||
@return Success. @throws[realloc, ERANGE] @allow */
|
||||
static int A_(array_splice)(struct A_(array) *restrict const a,
|
||||
const struct A_(array) *restrict const b,
|
||||
const size_t i0, const size_t i1) {
|
||||
const size_t a_range = i1 - i0, b_range = b ? b->size : 0;
|
||||
assert(a && a != b && i0 <= i1 && i1 <= a->size);
|
||||
if(a_range < b_range) { /* The output is bigger. */
|
||||
const size_t diff = b_range - a_range;
|
||||
if(!A_(array_buffer)(a, diff)) return 0;
|
||||
memmove(a->data + i1 + diff, a->data + i1,
|
||||
(a->size - i1) * sizeof *a->data);
|
||||
a->size += diff;
|
||||
} else if(b_range < a_range) { /* The output is smaller. */
|
||||
memmove(a->data + i0 + b_range, a->data + i1,
|
||||
(a->size - i1) * sizeof *a->data);
|
||||
a->size -= a_range - b_range;
|
||||
}
|
||||
if(b) memcpy(a->data + i0, b->data, b->size * sizeof *a->data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Box override information. */
|
||||
#define BOX_TYPE struct A_(array)
|
||||
#define BOX_CONTENT PA_(type) *
|
||||
#define BOX_ PA_
|
||||
#define BOX_MAJOR_NAME array
|
||||
#define BOX_MINOR_NAME ARRAY_NAME
|
||||
#define BOX_ACCESS
|
||||
#define BOX_CONTIGUOUS
|
||||
|
||||
#ifdef HAVE_ITERATE_H /* <!-- iterate */
|
||||
#include "iterate.h" /** \include */
|
||||
#endif /* iterate --> */
|
||||
|
||||
static void PA_(unused_base_coda)(void);
|
||||
static void PA_(unused_base)(void) {
|
||||
PA_(null)(); 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_iterator)(0); A_(array_iterator_at)(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);
|
||||
A_(array_clear)(0); A_(array_peek)(0); A_(array_pop)(0);
|
||||
A_(array_append)(0, 0); A_(array_splice)(0, 0, 0, 0);
|
||||
PA_(unused_base_coda)();
|
||||
}
|
||||
static void PA_(unused_base_coda)(void) { PA_(unused_base)(); }
|
||||
|
||||
#endif /* base code --> */
|
||||
|
||||
|
||||
#ifdef ARRAY_TRAIT /* <-- trait: Will be different on different includes. */
|
||||
#define BOX_TRAIT_NAME ARRAY_TRAIT
|
||||
#endif /* trait --> */
|
||||
|
||||
|
||||
#ifdef ARRAY_TO_STRING /* <!-- to string trait */
|
||||
#include "to_string.h" /** \include */
|
||||
#undef ARRAY_TO_STRING
|
||||
#ifndef ARRAY_TRAIT
|
||||
#define ARRAY_HAS_TO_STRING
|
||||
#endif
|
||||
#endif /* to string trait --> */
|
||||
|
||||
|
||||
#if defined(ARRAY_TEST) && !defined(ARRAY_TRAIT) /* <!-- test base */
|
||||
#include "../test/test_array.h"
|
||||
#endif /* test base --> */
|
||||
|
||||
|
||||
#if defined(ARRAY_COMPARE) || defined(ARRAY_IS_EQUAL) /* <!-- compare trait */
|
||||
#ifdef ARRAY_COMPARE /* <!-- cmp */
|
||||
#define COMPARE ARRAY_COMPARE
|
||||
#else /* cmp --><!-- eq */
|
||||
#define COMPARE_IS_EQUAL ARRAY_IS_EQUAL
|
||||
#endif /* eq --> */
|
||||
#include "compare.h" /** \include */
|
||||
#ifdef ARRAY_TEST /* <!-- test: this detects and outputs compare test. */
|
||||
#include "../test/test_array_compare.h"
|
||||
#endif /* test --> */
|
||||
#undef CMP_ /* From <compare.h>. */
|
||||
#undef CMPEXTERN_
|
||||
#ifdef ARRAY_COMPARE
|
||||
#undef ARRAY_COMPARE
|
||||
#else
|
||||
#undef ARRAY_IS_EQUAL
|
||||
#endif
|
||||
#endif /* compare trait --> */
|
||||
|
||||
|
||||
#ifdef ARRAY_EXPECT_TRAIT /* <!-- more */
|
||||
#undef ARRAY_EXPECT_TRAIT
|
||||
#else /* more --><!-- done */
|
||||
#undef BOX_TYPE
|
||||
#undef BOX_CONTENT
|
||||
#undef BOX_
|
||||
#undef BOX_MAJOR_NAME
|
||||
#undef BOX_MINOR_NAME
|
||||
#undef BOX_ACCESS
|
||||
#undef BOX_CONTIGUOUS
|
||||
#undef ARRAY_NAME
|
||||
#undef ARRAY_TYPE
|
||||
#undef ARRAY_MIN_CAPACITY
|
||||
#ifdef ARRAY_HAS_TO_STRING
|
||||
#undef ARRAY_HAS_TO_STRING
|
||||
#endif
|
||||
#ifdef ARRAY_TEST
|
||||
#undef ARRAY_TEST
|
||||
#endif
|
||||
#endif /* done --> */
|
||||
#ifdef ARRAY_TRAIT
|
||||
#undef ARRAY_TRAIT
|
||||
#undef BOX_TRAIT_NAME
|
||||
#endif
|
||||
#ifdef ARRAY_RESTRICT
|
||||
#undef ARRAY_RESTRICT
|
||||
#undef restrict
|
||||
#endif
|
@ -1,369 +0,0 @@
|
||||
/** @license 2022 Neil Edelman, distributed under the terms of the
|
||||
[MIT License](https://opensource.org/licenses/MIT).
|
||||
Is intended to use
|
||||
<https://github.com/scrollmapper/bible_databases/master/txt/KJV/>.
|
||||
@std C13 */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h> /* opendir readdir closedir */
|
||||
#include <unistd.h> /* chdir (POSIX) (because I'm lazy) */
|
||||
|
||||
|
||||
/* Dynamic contiguous string that is used to load files. */
|
||||
|
||||
#define ARRAY_NAME char
|
||||
#define ARRAY_TYPE char
|
||||
#include "../src/array.h"
|
||||
|
||||
/** Append a text file, `fn`, to `c`, and add a '\0'.
|
||||
@return The start of the appended file or null on error. A partial read is a
|
||||
failure. @throws[fopen, fread, malloc]
|
||||
@throws[EISEQ] The text file has embedded nulls.
|
||||
@throws[ERANGE] If the standard library does not follow POSIX. */
|
||||
static char *append_file(struct char_array *text, const char *const fn) {
|
||||
FILE *fp = 0;
|
||||
const size_t granularity = 1024;
|
||||
size_t nread, start;
|
||||
char *cursor;
|
||||
int success = 1;
|
||||
assert(text && fn);
|
||||
start = text->size;
|
||||
if(!(fp = fopen(fn, "r"))) goto catch;
|
||||
/* Read entire file in chunks. */
|
||||
do if(!(cursor = char_array_buffer(text, granularity))
|
||||
|| (nread = fread(cursor, 1, granularity, fp), ferror(fp))
|
||||
|| !char_array_append(text, nread)) goto catch;
|
||||
while(nread == granularity);
|
||||
/* File to `C` string. */
|
||||
if(!(cursor = char_array_new(text))) goto catch;
|
||||
*cursor = '\0';
|
||||
/* Binary files with embedded '\0' are not allowed; check just this read. */
|
||||
if(strchr(text->data + start, '\0') != cursor)
|
||||
{ errno = EILSEQ; goto catch; }
|
||||
goto finally;
|
||||
catch:
|
||||
if(!errno) errno = EILSEQ; /* Will never be true on POSIX. */
|
||||
success = 0;
|
||||
finally:
|
||||
if(fp) fclose(fp);
|
||||
return success ? text->data + start : 0;
|
||||
}
|
||||
|
||||
|
||||
/** Helper to parse unsigned; [`s`,`e`) => `n`. */
|
||||
static int parse_natural(const char *s, const char *const e, unsigned *const n) {
|
||||
unsigned accum = 0;
|
||||
while(s < e) {
|
||||
unsigned next = accum * 10 + (unsigned)(*s - '0');
|
||||
if(accum >= next) return errno = ERANGE, 0;
|
||||
accum = next;
|
||||
s++;
|
||||
}
|
||||
*n = accum;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Enumerate books. */
|
||||
|
||||
#define BOOKS \
|
||||
X(Genesis),\
|
||||
X(Exodus),\
|
||||
X(Leviticus),\
|
||||
X(Numbers),\
|
||||
X(Deuteronomy),\
|
||||
X(Joshua),\
|
||||
X(Judges),\
|
||||
X(Ruth),\
|
||||
X(ISamuel),\
|
||||
X(IISamuel),\
|
||||
X(IKings),\
|
||||
X(IIKings),\
|
||||
X(IChronicles),\
|
||||
X(IIChronicles),\
|
||||
X(Ezra),\
|
||||
X(Nehemiah),\
|
||||
X(Esther),\
|
||||
X(Job),\
|
||||
X(Psalms),\
|
||||
X(Proverbs),\
|
||||
X(Ecclesiastes),\
|
||||
X(Song_of_Solomon),\
|
||||
X(Isaiah),\
|
||||
X(Jeremiah),\
|
||||
X(Lamentations),\
|
||||
X(Ezekiel),\
|
||||
X(Daniel),\
|
||||
X(Hosea),\
|
||||
X(Joel),\
|
||||
X(Amos),\
|
||||
X(Obadiah),\
|
||||
X(Jonah),\
|
||||
X(Micah),\
|
||||
X(Nahum),\
|
||||
X(Habakkuk),\
|
||||
X(Zephaniah),\
|
||||
X(Haggai),\
|
||||
X(Zechariah),\
|
||||
X(Malachi),\
|
||||
\
|
||||
X(Matthew),\
|
||||
X(Mark),\
|
||||
X(Luke),\
|
||||
X(John),\
|
||||
X(Acts),\
|
||||
X(Romans),\
|
||||
X(ICorinthians),\
|
||||
X(IICorinthians),\
|
||||
X(Galatians),\
|
||||
X(Ephesians),\
|
||||
X(Philippians),\
|
||||
X(Colossians),\
|
||||
X(IThessalonians),\
|
||||
X(IIThessalonians),\
|
||||
X(ITimothy),\
|
||||
X(IITimothy),\
|
||||
X(Titus),\
|
||||
X(Philemon),\
|
||||
X(Hebrews),\
|
||||
X(James),\
|
||||
X(IPeter),\
|
||||
X(IIPeter),\
|
||||
X(IJohn),\
|
||||
X(IIJohn),\
|
||||
X(IIIJohn),\
|
||||
X(Jude),\
|
||||
X(Revelation),\
|
||||
X(KJV_BOOK_SIZE)
|
||||
|
||||
#define X(book) book
|
||||
enum kjv_book { BOOKS };
|
||||
#undef X
|
||||
#define X(book) #book
|
||||
static const char *kjv_book_string[] = { BOOKS };
|
||||
#undef X
|
||||
#undef BOOKS
|
||||
|
||||
|
||||
/* Parse filename of books. This works with
|
||||
<https://github.com/scrollmapper/bible_databases/tree/master/txt/KJV> */
|
||||
|
||||
/*!re2c /**/
|
||||
re2c:yyfill:enable = 0;
|
||||
re2c:define:YYCTYPE = char;
|
||||
natural = [1-9][0-9]*;
|
||||
whitespace = [ \t\v\f];
|
||||
word = [^ \t\v\f\n\x00]+;
|
||||
*/
|
||||
|
||||
/** `fn` contains "<number>[*].txt", sticks that in `book_no`, otherwise
|
||||
returns false. */
|
||||
static int kjv_filename(const char *fn, unsigned *const book_no) {
|
||||
const char *YYCURSOR = fn, *YYMARKER, *yyt1, *yyt2, *s0, *s1;
|
||||
assert(fn && book_no);
|
||||
/*!re2c /**/
|
||||
*
|
||||
{ return 0; }
|
||||
@s0 natural @s1 [^.\x00]* ".txt" "\x00"
|
||||
{ return parse_natural(s0, s1, book_no); }
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/* Parse book contents. */
|
||||
|
||||
struct lex {
|
||||
size_t line;
|
||||
const char *cursor;
|
||||
int error;
|
||||
unsigned chapter, verse, words;
|
||||
};
|
||||
|
||||
static struct lex lex(const char *cursor) {
|
||||
struct lex lex;
|
||||
assert(cursor);
|
||||
lex.line = 1;
|
||||
lex.cursor = cursor;
|
||||
lex.error = 0;
|
||||
lex.chapter = lex.verse = lex.words = 0;
|
||||
return lex;
|
||||
}
|
||||
|
||||
/*!conditions:re2c*/
|
||||
|
||||
static int lex_next_verse(struct lex *const lex) {
|
||||
const char *YYMARKER, *yyt1 = 0, *yyt2 = 0, *s0, *s1, *t0, *t1;
|
||||
enum YYCONDTYPE condition = yycline;
|
||||
/*!re2c /**/
|
||||
re2c:define:YYCURSOR = lex->cursor;
|
||||
re2c:define:YYGETCONDITION = "condition";
|
||||
re2c:define:YYSETCONDITION = "condition = @@;";
|
||||
re2c:define:YYGETCONDITION:naked = 1;
|
||||
re2c:define:YYSETCONDITION:naked = 1; */
|
||||
assert(lex && lex->cursor);
|
||||
lex->error = 0;
|
||||
scan:
|
||||
/*!re2c /**/
|
||||
<*> * { return errno = EILSEQ, lex->error = 1, 0; }
|
||||
<line> [^[\]\n\x00]* "\n" { lex->line++; goto scan; }
|
||||
<line> "\x00" { return 0; }
|
||||
<line> "[" @s0 natural @s1 ":" @t0 natural @t1 "]" => verse {
|
||||
if(!parse_natural(s0, s1, &lex->chapter)
|
||||
|| !parse_natural(t0, t1, &lex->verse))
|
||||
return errno = EILSEQ, lex->error = 1, 0;
|
||||
lex->words = 0;
|
||||
/*printf("%u:%u", lex->chapter, lex->verse);*/
|
||||
goto scan;
|
||||
}
|
||||
<verse> whitespace+ { goto scan; }
|
||||
<verse> @s0 word @s1 { lex->words++; goto scan; }
|
||||
<verse> "\n" { /*printf(" -> %u\n", lex->words);*/ lex->line++; return 1; }
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/* Reversible hash map to store data on bible. */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** <https://nullprogram.com/blog/2018/07/31/>
|
||||
<https://github.com/skeeto/hash-prospector> on `x`. */
|
||||
static uint32_t lowbias32(uint32_t x) {
|
||||
x ^= x >> 16;
|
||||
x *= 0x7feb352dU;
|
||||
x ^= x >> 15;
|
||||
x *= 0x846ca68bU;
|
||||
x ^= x >> 16;
|
||||
return x;
|
||||
}
|
||||
/* Inverts `x`. */
|
||||
static uint32_t lowbias32_r(uint32_t x) {
|
||||
x ^= x >> 16;
|
||||
x *= 0x43021123U;
|
||||
x ^= x >> 15 ^ x >> 30;
|
||||
x *= 0x1d69e2a5U;
|
||||
x ^= x >> 16;
|
||||
return x;
|
||||
}
|
||||
|
||||
/** Two hash-tables use the same structure. */
|
||||
union kjvcite {
|
||||
/* Overkill, but no initializing unused bits, 12 + 13 + 7 = 32. */
|
||||
struct { unsigned verse : 12, chapter : 13, book : 7; };
|
||||
uint32_t u32;
|
||||
};
|
||||
static uint32_t kjv_hash(const union kjvcite x) { return lowbias32(x.u32); }
|
||||
static union kjvcite kjv_unhash(const uint32_t x) {
|
||||
union kjvcite k;
|
||||
k.u32 = lowbias32_r(x);
|
||||
return k;
|
||||
}
|
||||
static void kjv_to_string(const union kjvcite x, char (*const a)[12])
|
||||
{ sprintf(*a, "%.4s%u:%u", kjv_book_string[x.book],
|
||||
(x.chapter + 1) % 1000, (x.verse + 1) % 1000); }
|
||||
|
||||
/** Derived information on verse word count. */
|
||||
static uint32_t verse_hash(const union kjvcite x) { return kjv_hash(x); }
|
||||
static union kjvcite verse_unhash(const uint32_t x) { return kjv_unhash(x); }
|
||||
static void verse_to_string(const union kjvcite x, char (*const a)[12])
|
||||
{ kjv_to_string(x, a); }
|
||||
#define TABLE_NAME verse
|
||||
#define TABLE_KEY union kjvcite
|
||||
#define TABLE_UINT uint32_t
|
||||
#define TABLE_VALUE unsigned
|
||||
#define TABLE_INVERSE
|
||||
#define TABLE_DEFAULT 0
|
||||
#define TABLE_TO_STRING
|
||||
#include "../src/table.h"
|
||||
|
||||
/* A set of verses. */
|
||||
static uint32_t kjvset_hash(const union kjvcite x) { return kjv_hash(x); }
|
||||
static union kjvcite kjvset_unhash(const uint32_t x) { return kjv_unhash(x); }
|
||||
static void kjvset_to_string(const union kjvcite x, char (*const a)[12])
|
||||
{ kjv_to_string(x, a); }
|
||||
#define TABLE_NAME kjvset
|
||||
#define TABLE_KEY union kjvcite
|
||||
#define TABLE_UINT uint32_t
|
||||
#define TABLE_INVERSE
|
||||
#define TABLE_TO_STRING
|
||||
#include "../src/table.h"
|
||||
|
||||
int main(void) {
|
||||
const char *const dir_kjv = "KJV";
|
||||
struct {
|
||||
struct char_array backing;
|
||||
struct verse_table verses;
|
||||
size_t words;
|
||||
} kjv = { 0 };
|
||||
DIR *dir = 0;
|
||||
struct dirent *de = 0;
|
||||
struct { size_t offset; int is; } build[KJV_BOOK_SIZE] = { 0 };
|
||||
enum kjv_book b = 0;
|
||||
int success = EXIT_SUCCESS, attempted_closedir = 0;
|
||||
errno = 0;
|
||||
|
||||
/* For all files in directory KJV with <#>*.txt, read into backing. */
|
||||
if(chdir(dir_kjv) == -1 || !(dir = opendir("."))) goto catch;
|
||||
while((de = readdir(dir))) {
|
||||
unsigned ordinal;
|
||||
char *unstable_book;
|
||||
if(!kjv_filename(de->d_name, &ordinal)) continue; /* Extract no. */
|
||||
/*fprintf(stderr, "<%s> ordinal: %u\n", de->d_name, ordinal);*/
|
||||
if(ordinal < 1 || ordinal > KJV_BOOK_SIZE)
|
||||
{ errno = ERANGE; goto catch; } /* Not in range. */
|
||||
if(build[b = ordinal - 1].is) /* Convert to zero-based. */
|
||||
{ errno = EDOM; goto catch; } /* Is duplicate. */
|
||||
if(!(unstable_book = append_file(&kjv.backing, de->d_name))) goto catch;
|
||||
build[b].is = 1;
|
||||
build[b].offset = (size_t)(unstable_book - kjv.backing.data);
|
||||
}
|
||||
if(attempted_closedir = 1, closedir(dir) == -1) goto catch; dir = 0;
|
||||
|
||||
/* Now backing is stable; count all the words for each verse. */
|
||||
for(b = 0; b < KJV_BOOK_SIZE; b++) {
|
||||
struct lex x;
|
||||
if(!build[b].is) { fprintf(stderr, "Missing book [%u]%s.\n",
|
||||
b + 1, kjv_book_string[b]); errno = EDOM; goto catch; }
|
||||
x = lex(kjv.backing.data + build[b].offset);
|
||||
while(lex_next_verse(&x)) {
|
||||
const union kjvcite cite
|
||||
= { .book = b, .chapter = x.chapter, .verse = x.verse };
|
||||
unsigned *words;
|
||||
switch(verse_table_assign(&kjv.verses, cite, &words)) {
|
||||
case TABLE_PRESENT: fprintf(stderr, "[%u]%s %u:%u duplicated.\n",
|
||||
b + 1, kjv_book_string[b], x.chapter, x.verse); errno = EDOM;
|
||||
case TABLE_ERROR: goto catch;
|
||||
case TABLE_ABSENT: break;
|
||||
}
|
||||
*words = x.words, kjv.words += x.words;
|
||||
}
|
||||
if(x.error) { fprintf(stderr, "[%u]%s on line %zu\n",
|
||||
b + 1, kjv_book_string[b], x.line); goto catch; }
|
||||
}
|
||||
|
||||
printf("words: %s\n", verse_table_to_string(&kjv.verses));
|
||||
printf("kjv: %zu total words\n", kjv.words);
|
||||
{
|
||||
union kjvcite c;
|
||||
struct verse_table_iterator it = verse_table_begin(&kjv.verses);
|
||||
unsigned *w;
|
||||
while(verse_table_next(&it, &c, &w))
|
||||
printf("%s %u:%u -> %u\n",
|
||||
kjv_book_string[c.book], c.chapter, c.verse, *w);
|
||||
c = (union kjvcite){ .book = Genesis, .chapter = 1, .verse = 1 };
|
||||
printf("1:1:1 -> %u\n", verse_table_get(&kjv.verses, c));
|
||||
}
|
||||
goto finally;
|
||||
catch:
|
||||
success = EXIT_FAILURE;
|
||||
if(de) fprintf(stderr, "While reading %s.\n", de->d_name);
|
||||
perror(de ? de->d_name : dir_kjv);
|
||||
if(dir && !attempted_closedir && closedir(dir) == -1) perror(dir_kjv);
|
||||
finally:
|
||||
verse_table_(&kjv.verses);
|
||||
char_array_(&kjv.backing);
|
||||
return success;
|
||||
}
|
@ -1,934 +0,0 @@
|
||||
/** @license 2019 Neil Edelman, distributed under the terms of the
|
||||
[MIT License](https://opensource.org/licenses/MIT).
|
||||
|
||||
@abstract Stand-alone header <src/table.h>; examples <test/test_table.c>;
|
||||
article <doc/table.pdf>. On a compatible workstation, `make` creates the
|
||||
test suite of the examples.
|
||||
|
||||
@subtitle Hash table
|
||||
|
||||
![Example of <string>table.](../doc/table.png)
|
||||
|
||||
<tag:<N>table> implements a set or map of <typedef:<PN>entry> as a hash table.
|
||||
It must be supplied <typedef:<PN>hash_fn> `<N>hash` and,
|
||||
<typedef:<PN>is_equal_fn> `<N>is_equal` or <typedef:<PN>unhash_fn> `<N>unhash`.
|
||||
|
||||
(Fixme: remove entry as public struct, this should be entirely private.)
|
||||
|
||||
@param[TABLE_NAME, TABLE_KEY]
|
||||
`<N>` that satisfies `C` naming conventions when mangled and a valid
|
||||
<typedef:<PN>key> associated therewith; required. `<PN>` is private, whose
|
||||
names are prefixed in a manner to avoid collisions.
|
||||
|
||||
@param[TABLE_INVERSE]
|
||||
By default it assumes that `<N>is_equal` is supplied; with this, instead
|
||||
requires `<N>unhash` satisfying <typedef:<PN>unhash_fn>.
|
||||
|
||||
@param[TABLE_VALUE]
|
||||
An optional type that is the payload of the key, thus making this a map or
|
||||
associative array.
|
||||
|
||||
@param[TABLE_UINT]
|
||||
This is <typedef:<PN>uint>, the unsigned type of hash hash of the key given by
|
||||
<typedef:<PN>hash_fn>; defaults to `size_t`.
|
||||
|
||||
@param[TABLE_DEFAULT]
|
||||
Default trait; a <typedef:<PN>value> used in <fn:<N>table<D>get>.
|
||||
|
||||
@param[TABLE_TO_STRING]
|
||||
To string trait `<STR>` contained in <src/to_string.h>. Require
|
||||
`<name>[<trait>]to_string` be declared as <typedef:<PSTR>to_string_fn>.
|
||||
|
||||
@param[TABLE_EXPECT_TRAIT, TABLE_TRAIT]
|
||||
Named traits are obtained by including `table.h` multiple times with
|
||||
`TABLE_EXPECT_TRAIT` and then subsequently including the name in
|
||||
`TABLE_TRAIT`.
|
||||
|
||||
@std C89 */
|
||||
|
||||
#if !defined(TABLE_NAME) || !defined(TABLE_KEY)
|
||||
#error Name TABLE_NAME or tag type TABLE_KEY undefined.
|
||||
#endif
|
||||
#if defined(TABLE_TRAIT) ^ defined(BOX_TYPE)
|
||||
#error TABLE_TRAIT name must come after TABLE_EXPECT_TRAIT.
|
||||
#endif
|
||||
#if defined(TABLE_TEST) && (!defined(TABLE_TRAIT) && !defined(TABLE_TO_STRING) \
|
||||
|| defined(TABLE_TRAIT) && !defined(TABLE_HAS_TO_STRING))
|
||||
#error Test requires to string.
|
||||
#endif
|
||||
|
||||
#ifndef TABLE_H /* <!-- idempotent */
|
||||
#define TABLE_H
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#if defined(TABLE_CAT_) || defined(TABLE_CAT) || defined(N_) || defined(PN_)
|
||||
#error Unexpected defines.
|
||||
#endif
|
||||
/* <Kernighan and Ritchie, 1988, p. 231>. */
|
||||
#define TABLE_CAT_(n, m) n ## _ ## m
|
||||
#define TABLE_CAT(n, m) TABLE_CAT_(n, m)
|
||||
#define N_(n) TABLE_CAT(TABLE_NAME, n)
|
||||
#define PN_(n) TABLE_CAT(table, N_(n))
|
||||
/* Use the sign bit to store out-of-band flags when a <typedef:<PN>uint>
|
||||
represents an address in the table, (such that range of an index is one bit
|
||||
less.) Choose representations that may save power? We cannot save this in an
|
||||
`enum` because we don't know maximum. */
|
||||
#define TABLE_M1 ((PN_(uint))~(PN_(uint))0) /* 2's compliment -1. */
|
||||
#define TABLE_HIGH ((TABLE_M1 >> 1) + 1) /* Cardinality must be 1111... */
|
||||
#define TABLE_END (TABLE_HIGH)
|
||||
#define TABLE_NULL (TABLE_HIGH + 1)
|
||||
#define TABLE_RESULT X(ERROR), X(ABSENT), X(PRESENT)
|
||||
#define X(n) TABLE_##n
|
||||
/** A result of modifying the table, of which `TABLE_ERROR` is false.
|
||||
|
||||
![A diagram of the result states.](../doc/put.png) */
|
||||
enum table_result { TABLE_RESULT };
|
||||
#undef X
|
||||
#define X(n) #n
|
||||
/** A static array of strings describing the <tag:table_result>. */
|
||||
static const char *const table_result_str[] = { TABLE_RESULT };
|
||||
#undef X
|
||||
#undef TABLE_RESULT
|
||||
#endif /* idempotent --> */
|
||||
|
||||
|
||||
#ifndef TABLE_TRAIT /* <!-- base code */
|
||||
|
||||
#ifndef TABLE_UINT
|
||||
#define TABLE_UINT size_t
|
||||
#endif
|
||||
/** <typedef:<PN>hash_fn> returns this hash type by `TABLE_UINT`, which must be
|
||||
be an unsigned integer. Places a simplifying limit on the maximum number of
|
||||
elements of half the cardinality. */
|
||||
typedef TABLE_UINT PN_(uint);
|
||||
|
||||
/** Valid tag type defined by `TABLE_KEY` used for keys. If `TABLE_INVERSE` is
|
||||
not defined, a copy of this value will be stored in the internal buckets. */
|
||||
typedef TABLE_KEY PN_(key);
|
||||
typedef const TABLE_KEY PN_(key_c); /* Works 90%? */
|
||||
|
||||
#if 0 /* <!-- documentation */
|
||||
/** A map from <typedef:<PN>key_c> onto <typedef:<PN>uint>, called `<N>hash`,
|
||||
that, ideally, should be easy to compute while minimizing duplicate addresses.
|
||||
Must be consistent for each value while in the table. If <typedef:<PN>key> is
|
||||
a pointer, one is permitted to have null in the domain. */
|
||||
typedef PN_(uint) (*PN_(hash_fn))(const PN_(key));
|
||||
#ifdef TABLE_INVERSE /* <!-- inv */
|
||||
/** Defining `TABLE_INVERSE` says <typedef:<PN>hash_fn> forms a bijection
|
||||
between the range in <typedef:<PN>key> and the image in <typedef:<PN>uint>,
|
||||
and the inverse is called `<N>unhash`. In this case, keys are not stored
|
||||
in the hash table, rather they are generated using this inverse-mapping. */
|
||||
typedef PN_(key) (*PN_(unhash_fn))(PN_(uint));
|
||||
#else /* inv --><!-- !inv */
|
||||
/** Equivalence relation between <typedef:<PN>key> that satisfies
|
||||
`<PN>is_equal_fn(a, b) -> <PN>hash(a) == <PN>hash(b)`, called `<N>is_equal`.
|
||||
If `TABLE_INVERSE` is set, there is no need for this function because the
|
||||
comparison is done directly in hash space. */
|
||||
typedef int (*PN_(is_equal_fn))(PN_(key_c) a, PN_(key_c) b);
|
||||
#endif /* !inv --> */
|
||||
#endif /* documentation --> */
|
||||
|
||||
#ifdef TABLE_VALUE /* <!-- value */
|
||||
/** Defining `TABLE_VALUE` produces an associative map, otherwise it is the
|
||||
same as <typedef:<PN>key>. */
|
||||
typedef TABLE_VALUE PN_(value);
|
||||
/** Defining `TABLE_VALUE` creates this map from <typedef:<PN>key> to
|
||||
<typedef:<PN>value>, as an interface with table. */
|
||||
struct N_(table_entry) { PN_(key) key; PN_(value) value; };
|
||||
/** If `TABLE_VALUE`, this is <tag:<N>table_entry>; otherwise, it's the same as
|
||||
<typedef:<PN>key>. */
|
||||
typedef struct N_(table_entry) PN_(entry);
|
||||
#else /* value --><!-- !value */
|
||||
typedef PN_(key) PN_(value);
|
||||
typedef PN_(key) PN_(entry);
|
||||
#endif /* !value --> */
|
||||
|
||||
/** @return Key from `e`. */
|
||||
static PN_(key) PN_(entry_key)(PN_(entry) e) {
|
||||
#ifdef TABLE_VALUE
|
||||
return e.key;
|
||||
#else
|
||||
return e;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Address is hash modulo size of table. Any occupied buckets at the head of
|
||||
the linked structure are closed, that is, the address equals the index. These
|
||||
form a linked table, possibly with other, open buckets that have the same
|
||||
address in vacant buckets. */
|
||||
struct PN_(bucket) {
|
||||
PN_(uint) next; /* Bucket index, including `TABLE_NULL` and `TABLE_END`. */
|
||||
PN_(uint) hash;
|
||||
#ifndef TABLE_INVERSE
|
||||
PN_(key) key;
|
||||
#endif
|
||||
#ifdef TABLE_VALUE
|
||||
PN_(value) value;
|
||||
#endif
|
||||
};
|
||||
|
||||
/** Gets the key of an occupied `bucket`. */
|
||||
static PN_(key) PN_(bucket_key)(const struct PN_(bucket) *const bucket) {
|
||||
assert(bucket && bucket->next != TABLE_NULL);
|
||||
#ifdef TABLE_INVERSE
|
||||
/* On `TABLE_INVERSE`, this function must be defined by the user. */
|
||||
return N_(unhash)(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` with the information of `bucket`. */
|
||||
static PN_(entry) PN_(to_entry)(struct PN_(bucket) *const bucket) {
|
||||
PN_(entry) e;
|
||||
assert(bucket);
|
||||
#ifdef TABLE_VALUE /* entry { <PN>key key; <PN>value value; } */
|
||||
e.key = PN_(bucket_key)(bucket);
|
||||
e.value = bucket->value;
|
||||
#else /* entry <PN>key */
|
||||
e = PN_(bucket_key)(bucket);
|
||||
#endif
|
||||
return e;
|
||||
}
|
||||
|
||||
/** Returns true if the `replace` replaces the `original`.
|
||||
(Shouldn't it be entry?) */
|
||||
typedef int (*PN_(policy_fn))(PN_(key) original, PN_(key) replace);
|
||||
|
||||
/** To initialize, see <fn:<N>table>, `TABLE_IDLE`, `{0}` (`C99`,) or being
|
||||
`static`. The fields should be treated as read-only; any modification is
|
||||
liable to cause the table to go into an invalid state.
|
||||
|
||||
![States.](../doc/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. Top is an index of the stack, potentially lazy: MSB
|
||||
stores whether this is a step ahead (which would make it less, the stack
|
||||
grows from the bottom,) otherwise it is right at the top, */
|
||||
PN_(uint) log_capacity, size, top;
|
||||
};
|
||||
|
||||
/** The capacity of a non-idle `table` is always a power-of-two. */
|
||||
static PN_(uint) PN_(capacity)(const struct N_(table) *const table)
|
||||
{ return assert(table && table->buckets && table->log_capacity >= 3),
|
||||
(PN_(uint))((PN_(uint))1 << table->log_capacity); }
|
||||
|
||||
/** @return Indexes the first closed bucket in the set of buckets with the same
|
||||
address from non-idle `table` given the `hash`. If the bucket is empty, it
|
||||
will have `next = TABLE_NULL` or it's own <fn:<PN>to_bucket_no> not equal to
|
||||
the index. */
|
||||
static PN_(uint) PN_(to_bucket_no)(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.) This is not the same as the
|
||||
iterator. @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_no)(table, bucket->hash);
|
||||
/* assert(next < capacity), */ next != b;
|
||||
to_next = next, next = table->buckets[next].next);
|
||||
return to_next != TABLE_NULL ? table->buckets + to_next : 0;
|
||||
}
|
||||
|
||||
/* <!-- stack functions */
|
||||
/** On return, the `top` of `table` will be empty and eager, but size is not
|
||||
incremented, leaving it in intermediate state. Amortized if you grow only. */
|
||||
static void PN_(grow_stack)(struct N_(table) *const table) {
|
||||
/* Subtract one for eager. */
|
||||
PN_(uint) top = (table->top & ~TABLE_HIGH) - !(table->top & TABLE_HIGH);
|
||||
assert(table && table->buckets && table->top && top < PN_(capacity)(table));
|
||||
while(table->buckets[top].next != TABLE_NULL) assert(top), top--;
|
||||
table->top = top; /* Eager, since one is allegedly going to fill it. */
|
||||
}
|
||||
/** Force the evaluation of the stack of `table`, thereby making it eager. This
|
||||
is like searching for a bucket in open-addressing. @order \O(`buckets`) */
|
||||
static void PN_(force_stack)(struct N_(table) *const table) {
|
||||
PN_(uint) top = table->top;
|
||||
if(top & TABLE_HIGH) { /* Lazy. */
|
||||
struct PN_(bucket) *bucket;
|
||||
top &= ~TABLE_HIGH;
|
||||
do bucket = table->buckets + ++top/*, assert(top < capacity)*/;
|
||||
while(bucket->next != TABLE_NULL
|
||||
&& PN_(to_bucket_no)(table, bucket->hash) == top);
|
||||
table->top = top; /* Eager. */
|
||||
}
|
||||
}
|
||||
/** Is `i` in `table` possibly on the stack? (The stack grows from the high.) */
|
||||
static int PN_(in_stack_range)(const struct N_(table) *const table,
|
||||
const PN_(uint) i) {
|
||||
return assert(table && table->buckets),
|
||||
(table->top & ~TABLE_HIGH) + !!(table->top & TABLE_HIGH) <= i;
|
||||
}
|
||||
/** Corrects newly-deleted `b` from `table` in the stack. */
|
||||
static void PN_(shrink_stack)(struct N_(table) *const table,
|
||||
const PN_(uint) b) {
|
||||
assert(table && table->buckets && b < PN_(capacity)(table));
|
||||
assert(table->buckets[b].next == TABLE_NULL);
|
||||
if(!PN_(in_stack_range)(table, b)) return;
|
||||
PN_(force_stack)(table); /* Only have room for 1 step of laziness. */
|
||||
assert(PN_(in_stack_range)(table, b)); /* I think this is assured? Think. */
|
||||
if(b != table->top) {
|
||||
struct PN_(bucket) *const prev = PN_(prev)(table, table->top);
|
||||
table->buckets[b] = table->buckets[table->top];
|
||||
prev->next = b;
|
||||
}
|
||||
table->buckets[table->top].next = TABLE_NULL;
|
||||
table->top |= TABLE_HIGH; /* Lazy. */
|
||||
}
|
||||
/** Moves the `m` index in non-idle `table`, to the top of collision stack.
|
||||
This may result in an inconsistent state; one is responsible for filling that
|
||||
hole and linking it with top. */
|
||||
static void PN_(move_to_top)(struct N_(table) *const table, const PN_(uint) m) {
|
||||
struct PN_(bucket) *move, *top, *prev;
|
||||
assert(table
|
||||
&& table->size < PN_(capacity)(table) && m < PN_(capacity)(table));
|
||||
PN_(grow_stack)(table); /* Leaves it in an eager state. */
|
||||
move = table->buckets + m, top = table->buckets + table->top;
|
||||
assert(move->next != TABLE_NULL && top->next == TABLE_NULL);
|
||||
if(prev = PN_(prev)(table, m)) prev->next = table->top; /* \O(|`bucket`|) */
|
||||
memcpy(top, move, sizeof *move), move->next = TABLE_NULL;
|
||||
}
|
||||
/* stack --> */
|
||||
|
||||
/** `TABLE_INVERSE` is injective, so in that case, we only compare hashes.
|
||||
@return `a` and `b`. */
|
||||
static int PN_(equal_buckets)(PN_(key_c) a, PN_(key_c) b) {
|
||||
#ifdef TABLE_INVERSE
|
||||
return (void)a, (void)b, 1;
|
||||
#else
|
||||
/* Must have this function declared. */
|
||||
return N_(is_equal)(a, b);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** `table` will be searched linearly for `key` which has `hash`. */
|
||||
static struct PN_(bucket) *PN_(query)(struct N_(table) *const table,
|
||||
PN_(key_c) 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_no)(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_no)(table, bucket1->hash)) return 0;
|
||||
while(hash != bucket1->hash
|
||||
|| !PN_(equal_buckets)(key, PN_(bucket_key)(bucket1))) {
|
||||
if(b2 == TABLE_END) return 0;
|
||||
bucket1 = table->buckets + (b0 = b1, b1 = b2);
|
||||
assert(b1 < PN_(capacity)(table) && PN_(in_stack_range)(table, b1)
|
||||
&& b1 != TABLE_NULL);
|
||||
b2 = bucket1->next;
|
||||
}
|
||||
#ifdef TABLE_DONT_SPLAY /* <!-- !splay: (No reason not to, practically.) */
|
||||
return bucket1;
|
||||
#undef TABLE_DONT_SPLAY
|
||||
#else /* !splay --><!-- splay: bring the MRU to the front. */
|
||||
if(b0 == TABLE_NULL) return bucket1;
|
||||
{
|
||||
struct PN_(bucket) *const bucket0 = table->buckets + b0,
|
||||
*const bucket_head = table->buckets + head, temp;
|
||||
bucket0->next = b2;
|
||||
memcpy(&temp, bucket_head, sizeof *bucket_head);
|
||||
memcpy(bucket_head, bucket1, sizeof *bucket1);
|
||||
memcpy(bucket1, &temp, sizeof temp);
|
||||
bucket_head->next = b1;
|
||||
return bucket_head;
|
||||
}
|
||||
#endif /* splay --> */
|
||||
}
|
||||
|
||||
/** Ensures that `table` has enough buckets to fill `n` more than the size. May
|
||||
invalidate and re-arrange the order.
|
||||
@return Success; otherwise, `errno` will be set. @throws[realloc]
|
||||
@throws[ERANGE] Tried allocating more then can fit in half <typedef:<PN>uint>
|
||||
or `realloc` doesn't follow [POSIX
|
||||
](https://pubs.opengroup.org/onlinepubs/009695399/functions/realloc.html). */
|
||||
static int PN_(buffer)(struct N_(table) *const table, const PN_(uint) n) {
|
||||
struct PN_(bucket) *buckets;
|
||||
const PN_(uint) log_c0 = table->log_capacity,
|
||||
c0 = log_c0 ? (PN_(uint))((PN_(uint))1 << log_c0) : 0;
|
||||
PN_(uint) log_c1, c1, size1, i, wait, mask;
|
||||
assert(table && table->size <= TABLE_HIGH
|
||||
&& (!table->buckets && !table->size && !log_c0 && !c0
|
||||
|| table->buckets && table->size <= c0 && log_c0>=3));
|
||||
/* Can we satisfy `n` growth from the buffer? */
|
||||
if(TABLE_M1 - table->size < n || TABLE_HIGH < (size1 = table->size + n))
|
||||
return errno = ERANGE, 0;
|
||||
if(table->buckets) log_c1 = log_c0, c1 = c0 ? c0 : 1;
|
||||
else log_c1 = 3, c1 = 8;
|
||||
while(c1 < size1) log_c1++, c1 <<= 1;
|
||||
if(log_c0 == log_c1) return 1;
|
||||
|
||||
/* Otherwise, need to allocate more. */
|
||||
if(!(buckets = realloc(table->buckets, sizeof *buckets * c1)))
|
||||
{ if(!errno) errno = ERANGE; return 0; }
|
||||
table->top = (c1 - 1) | TABLE_HIGH; /* No stack. */
|
||||
table->buckets = buckets, table->log_capacity = log_c1;
|
||||
|
||||
/* Initialize new values. Mask to identify the added bits. */
|
||||
{ struct PN_(bucket) *e = buckets + c0, *const e_end = buckets + c1;
|
||||
for( ; e < e_end; e++) e->next = TABLE_NULL; }
|
||||
mask = (PN_(uint))((((PN_(uint))1 << log_c0) - 1)
|
||||
^ (((PN_(uint))1 << log_c1) - 1));
|
||||
|
||||
/* Rehash most closed buckets in the lower half. Create waiting
|
||||
linked-stack by borrowing next. */
|
||||
wait = TABLE_END;
|
||||
for(i = 0; i < c0; i++) {
|
||||
struct PN_(bucket) *idx, *go;
|
||||
PN_(uint) g, hash;
|
||||
idx = table->buckets + i;
|
||||
if(idx->next == TABLE_NULL) continue;
|
||||
g = PN_(to_bucket_no)(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_no)(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_no)(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_no)(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
|
||||
bucket->key = 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
|
||||
bucket->value = 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_no)(table, hash));/* Closed. */
|
||||
if(bucket->next != TABLE_NULL) { /* Occupied. */
|
||||
int in_stack = PN_(to_bucket_no)(table, bucket->hash) != i;
|
||||
PN_(move_to_top)(table, i);
|
||||
bucket->next = in_stack ? TABLE_END : table->top;
|
||||
} else { /* Unoccupied. */
|
||||
bucket->next = TABLE_END;
|
||||
}
|
||||
table->size++;
|
||||
return bucket;
|
||||
}
|
||||
|
||||
/** Put `entry` in `table`. For collisions, only if `policy` exists and returns
|
||||
true do and displace it to `eject`, if non-null.
|
||||
@return A <tag:table_result>. @throws[malloc]
|
||||
@order Amortized \O(max bucket length); the key to another bucket may have to
|
||||
be moved to the top; the table might be full and have to be resized. */
|
||||
static enum table_result PN_(put)(struct N_(table) *const table,
|
||||
PN_(entry) entry, PN_(entry) *eject, const PN_(policy_fn) policy) {
|
||||
struct PN_(bucket) *bucket;
|
||||
const PN_(key) key = PN_(entry_key)(entry);
|
||||
/* This function must be defined by the user. */
|
||||
const PN_(uint) hash = N_(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_PRESENT;
|
||||
if(eject) *eject = PN_(to_entry)(bucket);
|
||||
result = TABLE_PRESENT;
|
||||
} else {
|
||||
if(!(bucket = PN_(evict)(table, hash))) return TABLE_ERROR;
|
||||
result = TABLE_ABSENT;
|
||||
}
|
||||
PN_(replace_entry)(bucket, entry, hash);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Is `x` not null? @implements `is_content` */
|
||||
static int PN_(is_element)(const struct PN_(bucket) *const x) { return !!x; }
|
||||
/* In no particular order, usually, but deterministic up to topology changes.
|
||||
@implements `iterator` */
|
||||
struct PN_(iterator) { struct N_(table) *table; PN_(uint) cur, prev; };
|
||||
/** Helper to skip the buckets of `it` that are not there.
|
||||
@return Whether it found another index. */
|
||||
static int PN_(skip)(struct PN_(iterator) *const it) {
|
||||
const struct N_(table) *const t = it->table;
|
||||
const PN_(uint) limit = PN_(capacity)(t);
|
||||
assert(it && it->table && it->table->buckets);
|
||||
while(it->cur < limit) {
|
||||
struct PN_(bucket) *const bucket = t->buckets + it->cur;
|
||||
if(bucket->next != TABLE_NULL) return 1;
|
||||
it->cur++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/** @return Before `table`. @implements `begin` */
|
||||
static struct PN_(iterator) PN_(iterator)(struct N_(table) *const table) {
|
||||
struct PN_(iterator) it; it.table = table, it.cur = 0; it.prev = TABLE_NULL;
|
||||
return it;
|
||||
}
|
||||
/** Advances `it` to the next element. @return Pointer to the current element
|
||||
or null. @implements `next` */
|
||||
static struct PN_(bucket) *PN_(next)(struct PN_(iterator) *const it) {
|
||||
assert(it);
|
||||
if(!it->table || !it->table->buckets) return 0;
|
||||
if(PN_(skip)(it))
|
||||
return it->prev = it->cur, it->table->buckets + it->cur++;
|
||||
it->table = 0, it->cur = 0;
|
||||
return 0;
|
||||
}
|
||||
/** Removes the entry at `it`. @return Success. */
|
||||
static int PN_(remove)(struct PN_(iterator) *const it) {
|
||||
struct N_(table) *table;
|
||||
PN_(uint) prev = it->prev;
|
||||
struct PN_(bucket) *previous = 0, *current;
|
||||
PN_(uint) prv = TABLE_NULL, crnt;
|
||||
assert(it);
|
||||
if(prev == TABLE_NULL) return 0;
|
||||
table = it->table;
|
||||
assert(it->table == it->table
|
||||
&& it->table->buckets && prev < PN_(capacity)(it->table));
|
||||
/* Egregious code reuse. :[ */
|
||||
current = it->table->buckets + prev, assert(current->next != TABLE_NULL);
|
||||
crnt = PN_(to_bucket_no)(it->table, current->hash);
|
||||
while(crnt != prev) assert(crnt < PN_(capacity)(it->table)),
|
||||
crnt = (previous = it->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->cur = 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;
|
||||
}
|
||||
|
||||
/** ![States](../doc/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) _; };
|
||||
|
||||
/** Zeroed data (not all-bits-zero) is initialized. @return An idle array.
|
||||
@order \Theta(1) @allow */
|
||||
static struct N_(table) N_(table)(void) {
|
||||
struct N_(table) table;
|
||||
table.buckets = 0; table.log_capacity = 0; table.size = 0; table.top = 0;
|
||||
return table;
|
||||
}
|
||||
|
||||
/** If `table` is not null, destroys and returns it to idle. @allow */
|
||||
static void N_(table_)(struct N_(table) *const table)
|
||||
{ if(table) free(table->buckets), *table = N_(table)(); }
|
||||
|
||||
/** Loads `table` (can be null) into `it`. @allow */
|
||||
static struct N_(table_iterator) N_(table_begin)(struct N_(table) *const
|
||||
table) { struct N_(table_iterator) it; it._ = PN_(iterator)(table);
|
||||
return it; }
|
||||
#ifdef TABLE_VALUE /* <!-- map */
|
||||
/** Advances `it`. @param[key, value] If non-null, the key or value is filled
|
||||
with the next element on return true. `value` is a pointer to the actual value
|
||||
in the map, only there if it is a map.
|
||||
@return Whether it had a next element. @allow */
|
||||
static int N_(table_next)(struct N_(table_iterator) *const it,
|
||||
PN_(key) *key, PN_(value) **value) {
|
||||
struct PN_(bucket) *bucket = PN_(next)(&it->_);
|
||||
if(!bucket) return 0;
|
||||
if(key) *key = PN_(bucket_key)(bucket);
|
||||
if(value) *value = &bucket->value;
|
||||
return 1;
|
||||
}
|
||||
#else /* map --><!-- set */
|
||||
/** Advances `it`, sets `key` on true. */
|
||||
static int N_(table_next)(struct N_(table_iterator) *const it, PN_(key) *key) {
|
||||
struct PN_(bucket) *bucket = PN_(next)(&it->_);
|
||||
if(!bucket) return 0;
|
||||
if(key) *key = PN_(bucket_key)(bucket);
|
||||
return 1;
|
||||
}
|
||||
#endif /* set --> */
|
||||
/** Removes the entry at `it`. Whereas <fn:<N>table_remove> invalidates the
|
||||
iterator, this corrects for a signal `it`.
|
||||
@return Success, or there was no entry at the iterator's position, (anymore.)
|
||||
@allow */
|
||||
static int N_(table_iterator_remove)(struct N_(table_iterator) *const it)
|
||||
{ return assert(it), PN_(remove)(&it->_); }
|
||||
|
||||
/** 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) {
|
||||
/* This function must be defined by the user. */
|
||||
return table && table->buckets
|
||||
? !!PN_(query)(table, key, N_(hash)(key)) : 0;
|
||||
}
|
||||
|
||||
/** @param[result] If null, behaves like <fn:<N>table_is>, otherwise, a
|
||||
<typedef:<PN>entry> which gets filled on true.
|
||||
@return Whether `key` is in `table` (which can be null.) @allow */
|
||||
static int N_(table_query)(struct N_(table) *const table, const PN_(key) key,
|
||||
PN_(entry) *result) {
|
||||
struct PN_(bucket) *bucket;
|
||||
/* This function must be defined by the user. */
|
||||
if(!table || !table->buckets
|
||||
|| !(bucket = PN_(query)(table, key, N_(hash)(key)))) return 0;
|
||||
if(result) *result = PN_(to_entry)(bucket);
|
||||
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;
|
||||
/* This function must be defined by the user. */
|
||||
return table && table->buckets
|
||||
&& (bucket = PN_(query)(table, key, N_(hash)(key)))
|
||||
? PN_(bucket_value)(bucket) : default_value;
|
||||
}
|
||||
|
||||
/** Puts `entry` in `table` only if absent.
|
||||
@return One of: `TABLE_ERROR`, tried putting the entry in the table but
|
||||
failed, the table is not modified; `TABLE_PRESENT`, does nothing if there is
|
||||
another entry with the same key; `TABLE_ABSENT`, put an entry in the table.
|
||||
@throws[realloc, ERANGE] On `TABLE_ERROR`.
|
||||
@order Average amortised \O(1); worst \O(n). @allow */
|
||||
static enum table_result N_(table_try)(struct N_(table) *const table,
|
||||
PN_(entry) entry) { return PN_(put)(table, entry, 0, 0); }
|
||||
|
||||
/** Callback in <fn:<N>table_update>.
|
||||
@return `original` and `replace` ignored, true.
|
||||
@implements <typedef:<PN>policy_fn> */
|
||||
static int PN_(always_replace)(const PN_(key) original,
|
||||
const PN_(key) replace) { return (void)original, (void)replace, 1; }
|
||||
|
||||
/** Puts `entry` in `table`.
|
||||
@return One of: `TABLE_ERROR`, the table is not modified; `TABLE_ABSENT`, the
|
||||
`entry` is put if the table; `TABLE_PRESENT` if non-null, `eject` will be
|
||||
filled with the previous 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) {
|
||||
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_ABSENT`, the
|
||||
`entry` is new; `TABLE_PRESENT`, the entry has the same key as some other
|
||||
entry. If `policy` returns true, `eject` will be filled;
|
||||
@throws[realloc, ERANGE] On `TABLE_ERROR`.
|
||||
@order Average amortised \O(1); worst \O(n). @allow */
|
||||
static enum table_result N_(table_policy)(struct N_(table) *const table,
|
||||
PN_(entry) entry, PN_(entry) *eject, const PN_(policy_fn) policy)
|
||||
{ return PN_(put)(table, entry, eject, policy); }
|
||||
|
||||
#ifdef TABLE_VALUE /* <!-- value */
|
||||
|
||||
/** On `TABLE_VALUE`, try to put `key` into `table`, and update `value` to be
|
||||
a pointer to the current value.
|
||||
@return `TABLE_ERROR` does not set `value`; `TABLE_ABSENT`, the `value` will
|
||||
be uninitialized; `TABLE_PRESENT`, gets the current `value`. @throws[malloc] */
|
||||
static enum table_result PN_(assign)(struct N_(table) *const table,
|
||||
PN_(key) key, PN_(value) **const value) {
|
||||
struct PN_(bucket) *bucket;
|
||||
const PN_(uint) hash = N_(hash)(key);
|
||||
enum table_result result;
|
||||
assert(table && value);
|
||||
if(table->buckets && (bucket = PN_(query)(table, key, hash))) {
|
||||
result = TABLE_PRESENT;
|
||||
} else {
|
||||
if(!(bucket = PN_(evict)(table, hash))) return TABLE_ERROR;
|
||||
PN_(replace_key)(bucket, key, hash);
|
||||
result = TABLE_ABSENT;
|
||||
}
|
||||
*value = &bucket->value;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** If `TABLE_VALUE` is defined. Try (see <fn:<N>table_try>) to put `key` into
|
||||
`table`, and store the associated value in a pointer `value`.
|
||||
@return `TABLE_ERROR` does not set `value`; `TABLE_ABSENT`, the `value` will
|
||||
point to uninitialized memory; `TABLE_PRESENT`, gets the current `value`, (but
|
||||
doesn't use the `key`.)
|
||||
@throws[malloc, ERANGE] On `TABLE_ERROR`. @allow */
|
||||
static enum table_result N_(table_assign)(struct N_(table) *const table,
|
||||
PN_(key) key, PN_(value) **const value)
|
||||
{ return PN_(assign)(table, key, value); }
|
||||
|
||||
#endif /* value --> */
|
||||
|
||||
/** Removes `key` from `table` (which could be null.)
|
||||
@return Whether that `key` was in `table`. @order Average \O(1), (hash
|
||||
distributes elements uniformly); worst \O(n). @allow */
|
||||
static int N_(table_remove)(struct N_(table) *const table,
|
||||
const PN_(key) key) {
|
||||
struct PN_(bucket) *current;
|
||||
/* This function must be defined by the user. */
|
||||
PN_(uint) crnt, prv = TABLE_NULL, nxt, hash = N_(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_no)(table, hash));
|
||||
if((nxt = current->next) == TABLE_NULL
|
||||
|| PN_(in_stack_range)(table, crnt)
|
||||
&& crnt != PN_(to_bucket_no)(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;
|
||||
}
|
||||
|
||||
/* Box override information. */
|
||||
#define BOX_TYPE struct N_(table)
|
||||
#define BOX_CONTENT struct PN_(bucket) *
|
||||
#define BOX_ PN_
|
||||
#define BOX_MAJOR_NAME table
|
||||
#define BOX_MINOR_NAME TABLE_NAME
|
||||
|
||||
#ifdef HAVE_ITERATE_H /* <!-- iterate */
|
||||
#include "iterate.h" /** \include */
|
||||
#endif /* iterate --> */
|
||||
|
||||
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);
|
||||
PN_(is_element)(0);
|
||||
N_(table)(); N_(table_)(0); N_(table_begin)(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_update)(0, e, 0); N_(table_policy)(0,e,0,0);
|
||||
N_(table_remove)(0, k); N_(table_iterator_remove)(0);
|
||||
#ifdef TABLE_VALUE
|
||||
N_(table_next)(0, 0, 0); N_(table_assign)(0, k, 0);
|
||||
#else
|
||||
N_(table_next)(0, 0);
|
||||
#endif
|
||||
PN_(unused_base_coda)();
|
||||
}
|
||||
static void PN_(unused_base_coda)(void) { PN_(unused_base)(); }
|
||||
|
||||
#endif /* base code --> */
|
||||
|
||||
|
||||
#ifdef TABLE_TRAIT /* <-- trait: Will be different on different includes. */
|
||||
#define BOX_TRAIT_NAME TABLE_TRAIT
|
||||
#endif /* trait --> */
|
||||
/* #ifdef TABLE_TRAIT
|
||||
#define N_D_(n, m) TABLE_CAT(N_(n), TABLE_CAT(TABLE_TRAIT, m))
|
||||
#else
|
||||
#define N_D_(n, m) TABLE_CAT(N_(n), m)
|
||||
#endif
|
||||
#define PN_D_(n, m) TABLE_CAT(table, N_D_(n, m)) */
|
||||
|
||||
|
||||
#ifdef TABLE_TO_STRING /* <!-- to string trait */
|
||||
/** Private `bucket` would be a confusing thing with which to call to string to
|
||||
convert `z`. Insert an extra level of indirection to call this with the key. */
|
||||
static void N_(to_string_thunk)(const struct PN_(bucket) *const bucket,
|
||||
char (*const z)[12]) {
|
||||
/* This function must be defined by the user. */
|
||||
N_(to_string)(PN_(bucket_key)(bucket), z);
|
||||
}
|
||||
#define TO_STRING_THUNK_(n) TABLE_CAT(n, thunk)
|
||||
#define TO_STRING_LEFT '{'
|
||||
#define TO_STRING_RIGHT '}'
|
||||
#include "to_string.h" /** \include */
|
||||
#undef TABLE_TO_STRING
|
||||
#ifndef TABLE_TRAIT
|
||||
#define TABLE_HAS_TO_STRING
|
||||
#endif
|
||||
#endif /* to string trait --> */
|
||||
|
||||
|
||||
#if defined(TABLE_TEST) && !defined(TABLE_TRAIT) /* <!-- test base */
|
||||
#include "../test/test_table.h"
|
||||
#endif /* test base --> */
|
||||
|
||||
|
||||
#ifdef TABLE_DEFAULT /* <!-- default trait */
|
||||
#ifdef TABLE_TRAIT
|
||||
#define N_D_(n, m) TABLE_CAT(N_(n), TABLE_CAT(TABLE_TRAIT, m))
|
||||
#define PN_D_(n, m) TABLE_CAT(table, N_D_(n, m))
|
||||
#else
|
||||
#define N_D_(n, m) TABLE_CAT(N_(n), m)
|
||||
#define PN_D_(n, m) TABLE_CAT(table, N_D_(n, m))
|
||||
#endif
|
||||
/* #include "../test/test_table_default.h", just test manually. */
|
||||
/** This is functionally identical to <fn:<N>table_get_or>, but a with a trait
|
||||
specifying a constant default value.
|
||||
@return The value associated with `key` in `table`, (which can be null.) If
|
||||
no such value exists, the `TABLE_DEFAULT` is returned.
|
||||
@order Average \O(1); worst \O(n). @allow */
|
||||
static PN_(value) N_D_(table, get)(struct N_(table) *const table,
|
||||
const PN_(key) key) {
|
||||
struct PN_(bucket) *bucket;
|
||||
/* `TABLE_DEFAULT` is a valid <tag:<PN>value>. */
|
||||
const PN_(value) PN_D_(default, value) = (TABLE_DEFAULT);
|
||||
/* Function `<N>hash` must be defined by the user. */
|
||||
return table && table->buckets
|
||||
&& (bucket = PN_(query)(table, key, N_(hash)(key)))
|
||||
? PN_(bucket_value)(bucket) : PN_D_(default, value);
|
||||
}
|
||||
static void PN_D_(unused, default_coda)(void);
|
||||
static void PN_D_(unused, default)(void) { PN_(key) k; memset(&k, 0, sizeof k);
|
||||
N_D_(table, get)(0, k); PN_D_(unused, default_coda)(); }
|
||||
static void PN_D_(unused, default_coda)(void) { PN_D_(unused, default)(); }
|
||||
#undef N_D_
|
||||
#undef PN_D_
|
||||
#undef TABLE_DEFAULT
|
||||
#endif /* default trait --> */
|
||||
|
||||
|
||||
#ifdef TABLE_EXPECT_TRAIT /* <!-- more */
|
||||
#undef TABLE_EXPECT_TRAIT
|
||||
#else /* more --><!-- done */
|
||||
#undef BOX_TYPE
|
||||
#undef BOX_CONTENT
|
||||
#undef BOX_
|
||||
#undef BOX_MAJOR_NAME
|
||||
#undef BOX_MINOR_NAME
|
||||
#undef TABLE_NAME
|
||||
#undef TABLE_KEY
|
||||
#undef TABLE_UINT
|
||||
#undef TABLE_HASH
|
||||
#ifdef TABLE_IS_EQUAL
|
||||
#undef TABLE_IS_EQUAL
|
||||
#else
|
||||
#undef TABLE_INVERSE
|
||||
#endif
|
||||
#ifdef TABLE_VALUE
|
||||
#undef TABLE_VALUE
|
||||
#endif
|
||||
#ifdef TABLE_HAS_TO_STRING
|
||||
#undef TABLE_HAS_TO_STRING
|
||||
#endif
|
||||
#ifdef TABLE_TEST
|
||||
#undef TABLE_TEST
|
||||
#endif
|
||||
#endif /* done --> */
|
||||
#ifdef TABLE_TRAIT
|
||||
#undef TABLE_TRAIT
|
||||
#undef BOX_TRAIT_NAME
|
||||
#endif
|
@ -1,171 +0,0 @@
|
||||
/* @license 2020 Neil Edelman, distributed under the terms of the
|
||||
[MIT License](https://opensource.org/licenses/MIT).
|
||||
|
||||
@subtitle To string trait
|
||||
|
||||
Interface defined by box. Requires `<NAME>[_<TRAIT>]_to_string` be declared as
|
||||
a <typedef:<PSTR>to_string_fn>.
|
||||
|
||||
@param[TO_STRING_LEFT, TO_STRING_RIGHT]
|
||||
7-bit characters, defaults to '(' and ')'.
|
||||
|
||||
@param[TO_STRING_EXTERN, TO_STRING_INTERN]
|
||||
Normally the space to put the temporary strings is static, one per file. With
|
||||
this, it's possible to have a global storage to save space: have one file have
|
||||
`TO_STRING_INTERN` as the first box, the other files `TO_STRING_EXTERN`. This
|
||||
is unsynchronized.
|
||||
|
||||
@fixme `extern` untested.
|
||||
|
||||
@std C89 */
|
||||
|
||||
#if !defined(BOX_TYPE) || !defined(BOX_CONTENT) || !defined(BOX_) \
|
||||
|| !defined(BOX_MAJOR_NAME) || !defined(BOX_MINOR_NAME) \
|
||||
|| defined(STR_) || defined(STREXTERN_)
|
||||
#error Unexpected preprocessor symbols.
|
||||
#endif
|
||||
|
||||
#if defined(TO_STRING_H) \
|
||||
&& (defined(TO_STRING_EXTERN) || defined(TO_STRING_INTERN)) /* <!-- not */
|
||||
#error Should be the on the first to_string in the compilation unit.
|
||||
#else /* not --><!-- !not */
|
||||
#if defined(TO_STRING_EXTERN) && defined(TO_STRING_INTERN) /* <!-- two */
|
||||
#error These can not be defined together.
|
||||
#endif /* two --> */
|
||||
#endif /* !not --> */
|
||||
|
||||
#ifndef TO_STRING_H /* <!-- idempotent */
|
||||
#define TO_STRING_H
|
||||
#include <string.h>
|
||||
#if defined(TO_STRING_CAT_) || defined(TO_STRING_CAT) || defined(PSTR_)
|
||||
#error Unexpected preprocessor symbols.
|
||||
#endif
|
||||
/* <Kernighan and Ritchie, 1988, p. 231>. */
|
||||
#define TO_STRING_CAT_(n, m) n ## _ ## m
|
||||
#define TO_STRING_CAT(n, m) TO_STRING_CAT_(n, m)
|
||||
#define PSTR_(n) TO_STRING_CAT(to_string, STR_(n))
|
||||
#if defined(TO_STRING_EXTERN) || defined(TO_STRING_INTERN) /* <!-- ntern */
|
||||
extern char to_string_buffers[4][256];
|
||||
extern const unsigned to_string_buffers_no;
|
||||
extern unsigned to_string_i;
|
||||
#ifdef TO_STRING_INTERN /* <!-- intern */
|
||||
char to_string_buffers[4][256];
|
||||
const unsigned to_string_buffers_no = sizeof to_string_buffers
|
||||
/ sizeof *to_string_buffers, to_string_buffer_size
|
||||
= sizeof *to_string_buffers / sizeof **to_string_buffers;
|
||||
unsigned to_string_buffer_i;
|
||||
#endif /* intern --> */
|
||||
#else /* ntern --><!-- static */
|
||||
static char to_string_buffers[4][256];
|
||||
static const unsigned to_string_buffers_no = sizeof to_string_buffers
|
||||
/ sizeof *to_string_buffers, to_string_buffer_size
|
||||
= sizeof *to_string_buffers / sizeof **to_string_buffers;
|
||||
static unsigned to_string_buffer_i;
|
||||
#endif /* static --> */
|
||||
#endif /* idempotent --> */
|
||||
|
||||
#ifndef TO_STRING_LEFT
|
||||
#define TO_STRING_LEFT '('
|
||||
#endif
|
||||
#ifndef TO_STRING_RIGHT
|
||||
#define TO_STRING_RIGHT ')'
|
||||
#endif
|
||||
|
||||
#ifndef BOX_TRAIT_NAME /* <!-- !trait */
|
||||
#define STR_(n) TO_STRING_CAT(TO_STRING_CAT(BOX_MINOR_NAME, BOX_MAJOR_NAME), n)
|
||||
#define STREXTERN_(n) TO_STRING_CAT(BOX_MINOR_NAME, n)
|
||||
#else /* !trait --><!-- trait */
|
||||
#define STR_(n) TO_STRING_CAT(TO_STRING_CAT(BOX_MINOR_NAME, BOX_MAJOR_NAME), \
|
||||
TO_STRING_CAT(BOX_TRAIT_NAME, n))
|
||||
#define STREXTERN_(n) TO_STRING_CAT(TO_STRING_CAT(BOX_MINOR_NAME, \
|
||||
BOX_TRAIT_NAME), n)
|
||||
#endif /* trait --> */
|
||||
|
||||
/* Provides an extra level of indirection for boxes if they need it. */
|
||||
#ifndef TO_STRING_THUNK_
|
||||
#define TO_STRING_THUNK_(n) n
|
||||
#endif
|
||||
|
||||
/* Hopefully gets rid of any nestled-qualifiers, but only when appropriate. */
|
||||
#ifndef TO_STRING_CAST
|
||||
#define TO_STRING_CAST (void *)
|
||||
#endif
|
||||
|
||||
typedef BOX_TYPE PSTR_(box);
|
||||
typedef BOX_CONTENT PSTR_(element);
|
||||
typedef const BOX_CONTENT PSTR_(element_c); /* Assumes a lot. */
|
||||
|
||||
/** <src/to_string.h>: responsible for turning the read-only argument into a
|
||||
12-`char` null-terminated output string. The first argument should be a
|
||||
read-only reference to an element and the second a pointer to the bytes. */
|
||||
typedef void (*PSTR_(to_string_fn))(const PSTR_(element), char (*)[12]);
|
||||
/* _Nb_: this is for documentation only; there is no way to get a general
|
||||
read-only type which what we are supplied. Think of nested pointers. */
|
||||
|
||||
/** <src/to_string.h>: print the contents of `box` in a static string buffer of
|
||||
256 bytes, with limitations of only printing 4 things at a time.
|
||||
@return Address of the static buffer. @order \Theta(1) @allow */
|
||||
static const char *STR_(to_string)(const PSTR_(box) *const box) {
|
||||
const char comma = ',', space = ' ', ellipsis[] = "…",
|
||||
left = TO_STRING_LEFT, right = TO_STRING_RIGHT;
|
||||
const size_t ellipsis_len = sizeof ellipsis - 1;
|
||||
char *const buffer = to_string_buffers[to_string_buffer_i++], *b = buffer;
|
||||
size_t advance;
|
||||
PSTR_(element) x;
|
||||
struct BOX_(iterator) it;
|
||||
int is_sep = 0;
|
||||
/* Minimum size: "(" "XXXXXXXXXXX" "," "…" ")" "\0". */
|
||||
assert(box && !(to_string_buffers_no & (to_string_buffers_no - 1))
|
||||
&& to_string_buffer_size >= 1 + 11 + 1 + ellipsis_len + 1 + 1);
|
||||
/* Advance the buffer for next time. */
|
||||
to_string_buffer_i &= to_string_buffers_no - 1;
|
||||
{ /* We do not modify `box`, but the compiler doesn't know that. */
|
||||
PSTR_(box) *promise_box;
|
||||
memcpy(&promise_box, &box, sizeof box);
|
||||
it = BOX_(iterator)(promise_box);
|
||||
}
|
||||
*b++ = left;
|
||||
while(BOX_(is_element)(x = BOX_(next)(&it))) {
|
||||
/* One must have this function declared! */
|
||||
TO_STRING_THUNK_(STREXTERN_(to_string))(TO_STRING_CAST
|
||||
x, (char (*)[12])b);
|
||||
/* Paranoid about '\0'; wastes 1 byte of 12, but otherwise confusing. */
|
||||
for(advance = 0; *b != '\0' && advance < 11; b++, advance++);
|
||||
is_sep = 1, *b++ = comma, *b++ = space;
|
||||
/* 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)(BOX_(next)(&it))) goto ellipsis;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
if(is_sep) b -= 2;
|
||||
*b++ = right;
|
||||
goto terminate;
|
||||
ellipsis:
|
||||
b--;
|
||||
memcpy(b, ellipsis, ellipsis_len), b += ellipsis_len;
|
||||
*b++ = right;
|
||||
terminate:
|
||||
*b++ = '\0';
|
||||
assert(b - buffer <= to_string_buffer_size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void PSTR_(unused_to_string_coda)(void);
|
||||
static void PSTR_(unused_to_string)(void)
|
||||
{ STR_(to_string)(0); PSTR_(unused_to_string_coda)(); }
|
||||
static void PSTR_(unused_to_string_coda)(void) { PSTR_(unused_to_string)(); }
|
||||
|
||||
#undef STR_
|
||||
#undef STREXTERN_
|
||||
#undef TO_STRING_CAST
|
||||
#undef TO_STRING_THUNK_
|
||||
#ifdef TO_STRING_EXTERN
|
||||
#undef TO_STRING_EXTERN
|
||||
#endif
|
||||
#ifdef TO_STRING_INTERN
|
||||
#undef TO_STRING_INTERN
|
||||
#endif
|
||||
#undef TO_STRING_LEFT
|
||||
#undef TO_STRING_RIGHT
|
Loading…
Reference in New Issue
Block a user