diff --git a/kjv/Makefile b/kjv/Makefile new file mode 100644 index 0000000..13a5e89 --- /dev/null +++ b/kjv/Makefile @@ -0,0 +1,242 @@ +# 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) diff --git a/kjv/src/array.h b/kjv/src/array.h new file mode 100644 index 0000000..2b0b23d --- /dev/null +++ b/kjv/src/array.h @@ -0,0 +1,430 @@ +/** @license 2016 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + @abstract Stand-alone header ; examples ; on a + compatible workstation, `make` creates the test suite of the examples. + + @subtitle Contiguous dynamic array + + ![Example of array.](../doc/array.png) + + array> is a dynamic array that stores contiguous 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] + `` 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[ARRAY_COMPARE, ARRAY_IS_EQUAL] + Compare `` trait contained in . Requires + `[]compare` to be declared as compare_fn> or + `[]is_equal` to be declared as bipredicate_fn>, + respectfully, (but not both.) + + @param[ARRAY_TO_STRING] + To string `` trait contained in . Requires + `[]to_string` be declared as 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 /* */ + +#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 /* */ + +/** 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 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 /* */ + +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 /* */ + + +#if defined(ARRAY_TEST) && !defined(ARRAY_TRAIT) /* */ + + +#if defined(ARRAY_COMPARE) || defined(ARRAY_IS_EQUAL) /* */ +#include "compare.h" /** \include */ +#ifdef ARRAY_TEST /* */ +#undef CMP_ /* From . */ +#undef CMPEXTERN_ +#ifdef ARRAY_COMPARE +#undef ARRAY_COMPARE +#else +#undef ARRAY_IS_EQUAL +#endif +#endif /* compare trait --> */ + + +#ifdef ARRAY_EXPECT_TRAIT /* */ +#ifdef ARRAY_TRAIT +#undef ARRAY_TRAIT +#undef BOX_TRAIT_NAME +#endif +#ifdef ARRAY_RESTRICT +#undef ARRAY_RESTRICT +#undef restrict +#endif diff --git a/kjv/src/kjv.h b/kjv/src/kjv.h new file mode 100644 index 0000000..104531d --- /dev/null +++ b/kjv/src/kjv.h @@ -0,0 +1 @@ +int kjv_filename(const char *, unsigned *); diff --git a/kjv/src/kjv.re_c.c b/kjv/src/kjv.re_c.c new file mode 100644 index 0000000..e32eecf --- /dev/null +++ b/kjv/src/kjv.re_c.c @@ -0,0 +1,40 @@ +/** @license 2022 Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + Is intended to use + . */ + +#include "../src/kjv.h" +#include +#include +#include /* debug */ + +static int parse_natural(const char *s, const char *const e, unsigned *const n) { + unsigned a = 0; + while(s < e) { + unsigned b = a * 10 + (unsigned)(*s - '0'); + if(a >= b) return errno = ERANGE, 0; + a = b; + s++; + } + *n = a; + return 1; +} + +/*!re2c /**/ +re2c:yyfill:enable = 0; +re2c:define:YYCTYPE = char; +sentinel = "\x00"; +newline = "\n"; +natural = [1-9][0-9]*; +*/ + +int kjv_filename(const char *fn, unsigned *book_no) { + const char *YYCURSOR = fn, *YYMARKER, *yyt1, *yyt2, *s0, *s1; + assert(fn && book_no); + /*!re2c /**/ + * + { return 0; } + @s0 natural @s1 [^.\x00]* ".txt" sentinel + { return parse_natural(s0, s1, book_no); } + */ +} diff --git a/kjv/src/main.c b/kjv/src/main.c new file mode 100644 index 0000000..53c611c --- /dev/null +++ b/kjv/src/main.c @@ -0,0 +1,87 @@ +/** @license 20xx Neil Edelman, distributed under the terms of the + [GNU General Public License 3](https://opensource.org/licenses/GPL-3.0). + @license 20xx Neil Edelman, distributed under the terms of the + [MIT License](https://opensource.org/licenses/MIT). + + This is a standard C file. + + @std C89 */ + +#include "kjv.h" +#include +#include +#include +#include +#include /* opendir readdir closedir */ +#include /* chdir (POSIX) (because I'm lazy) */ + +#define ARRAY_NAME char +#define ARRAY_TYPE char +#include "array.h" + +/** Append a text file, `fn`, to `c`, and add a '\0'. + @return Success. A partial read is failure. @throws[fopen, fread, malloc] + @throws[EISEQ] The text file has embedded nulls. + @throws[ERANGE] If the standard library does not follow POSIX. */ +static int append_file(struct char_array *c, const char *const fn) { + FILE *fp = 0; + const size_t granularity = 1024; + size_t nread; + char *cursor; + int success = 0; + assert(c && fn); + if(!(fp = fopen(fn, "r"))) goto catch; + /* Read entire file in chunks. */ + do if(!(cursor = char_array_buffer(c, granularity)) + || (nread = fread(cursor, 1, granularity, fp), ferror(fp)) + || !char_array_append(c, nread)) goto catch; + while(nread == granularity); + /* File to `C` string. */ + if(!(cursor = char_array_new(c))) goto catch; + *cursor = '\0'; + /* Binary files with embedded '\0' are not allowed. */ + if(strchr(c->data, '\0') != cursor) { errno = EILSEQ; goto catch; } + { success = 1; goto finally; } +catch: + if(!errno) errno = EILSEQ; /* Will never be true on POSIX. */ +finally: + if(fp) fclose(fp); + return success; +} + +int main(void) { + int success = EXIT_SUCCESS; + DIR *dir = 0; + struct dirent *de = 0; + struct char_array book[66] = { 0 }; + const unsigned book_size = sizeof book / sizeof *book; + unsigned i; + errno = 0; + + /* Read all files in . */ + if(chdir("KJV") == -1 || !(dir = opendir("."))) goto catch; + while((de = readdir(dir))) { + unsigned book_no; + if(!kjv_filename(de->d_name, &book_no)) + { fprintf(stderr, "Ignored <%s>.\n", de->d_name); continue; } + printf("<%s> book_no: %u\n", de->d_name, book_no); + if(!book_no || book_no > book_size || book[book_no - 1].data) + { errno = ERANGE; goto catch; } + if(!append_file(book + book_no - 1, de->d_name)) goto catch; + } + closedir(dir), de = 0, dir = 0; + + /* Check to see if all books are accounted for. */ + for(i = 0; i < book_size; i++) + if(!book[i].data) { errno = EDOM; goto catch; } + + /**/ + goto finally; +catch: + success = EXIT_FAILURE; + perror(de ? de->d_name : "kjv"); + if(dir && closedir(dir)) perror("dir"); +finally: + for(i = 0; i < book_size; i++) char_array_(&book[i]); + return success; +} diff --git a/kjv/src/to_string.h b/kjv/src/to_string.h new file mode 100644 index 0000000..affdc62 --- /dev/null +++ b/kjv/src/to_string.h @@ -0,0 +1,171 @@ +/* @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 `[_]_to_string` be declared as + a 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)) /* */ +#endif /* !not --> */ + +#ifndef TO_STRING_H /* */ +#else /* ntern --> */ +#endif /* idempotent --> */ + +#ifndef TO_STRING_LEFT +#define TO_STRING_LEFT '(' +#endif +#ifndef TO_STRING_RIGHT +#define TO_STRING_RIGHT ')' +#endif + +#ifndef BOX_TRAIT_NAME /* */ + +/* 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. */ + +/** : 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. */ + +/** : 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