summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2019-01-01 01:01:19 -0500
committerJune McEnroe <june@causal.agency>2019-01-01 01:01:19 -0500
commitac94da01512011011c347e692f640dddf473f855 (patch)
treebcaaad04f1f4257cd6fb2f367d506c74b530eb84
parentRename rec to wat (diff)
downloadsrc-ac94da01512011011c347e692f640dddf473f855.tar.gz
src-ac94da01512011011c347e692f640dddf473f855.zip
Remove edi (again)
-rw-r--r--bin/edi/.gitignore4
-rw-r--r--bin/edi/Makefile35
-rw-r--r--bin/edi/buffer.c168
-rw-r--r--bin/edi/edi.c66
-rw-r--r--bin/edi/edi.h116
-rw-r--r--bin/edi/file.c115
-rw-r--r--bin/edi/iter.c109
-rw-r--r--bin/edi/log.c50
-rw-r--r--bin/edi/store.c211
-rw-r--r--bin/edi/table.c171
10 files changed, 0 insertions, 1045 deletions
diff --git a/bin/edi/.gitignore b/bin/edi/.gitignore
deleted file mode 100644
index c76928a8..00000000
--- a/bin/edi/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-*.o
-*.t
-edi
-tags
diff --git a/bin/edi/Makefile b/bin/edi/Makefile
deleted file mode 100644
index e7f16b39..00000000
--- a/bin/edi/Makefile
+++ /dev/null
@@ -1,35 +0,0 @@
-CFLAGS += -Wall -Wextra -Wpedantic
-LDLIBS = -lcursesw
-
-OBJS += buffer.o
-OBJS += edi.o
-OBJS += file.o
-OBJS += iter.o
-OBJS += log.o
-OBJS += store.o
-OBJS += table.o
-
-TESTS += buffer.t
-TESTS += iter.t
-TESTS += table.t
-
-all: tags edi test
-
-tags: *.h *.c
-	ctags -w *.h *.c
-
-edi: $(OBJS)
-	$(CC) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@
-
-$(OBJS): edi.h
-
-test: $(TESTS)
-	set -e; $(TESTS:%=./%;)
-
-.SUFFIXES: .t
-
-.c.t:
-	$(CC) $(CFLAGS) -DTEST $(LDFLAGS) $< $(LDLIBS) -o $@
-
-clean:
-	rm -f tags edi $(OBJS) $(TESTS)
diff --git a/bin/edi/buffer.c b/bin/edi/buffer.c
deleted file mode 100644
index 3852872c..00000000
--- a/bin/edi/buffer.c
+++ /dev/null
@@ -1,168 +0,0 @@
-/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <err.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sysexits.h>
-#include <wchar.h>
-
-#include "edi.h"
-
-static struct Block *blockAlloc(struct Block *prev, size_t cap) {
-	size_t size = sizeof(struct Block) + sizeof(wchar_t) * cap;
-	struct Block *block = malloc(size);
-	if (!block) err(EX_OSERR, "malloc");
-	block->prev = prev;
-	block->len = 0;
-	return block;
-}
-
-struct Buffer bufferAlloc(size_t cap) {
-	return (struct Buffer) {
-		.cap = cap,
-		.block = blockAlloc(NULL, cap),
-	};
-}
-
-void bufferFree(struct Buffer *buf) {
-	struct Block *prev;
-	for (struct Block *it = buf->block; it; it = prev) {
-		prev = it->prev;
-		free(it);
-	}
-}
-
-void bufferSlice(struct Buffer *buf) {
-	buf->slice.ptr = &buf->block->chars[buf->block->len];
-	buf->slice.len = 0;
-}
-
-void bufferPush(struct Buffer *buf, wchar_t ch) {
-	if (buf->block->len == buf->cap) {
-		if (buf->slice.len == buf->cap) buf->cap *= 2;
-		buf->block = blockAlloc(buf->block, buf->cap);
-		memcpy(
-			buf->block->chars, buf->slice.ptr,
-			sizeof(wchar_t) * buf->slice.len
-		);
-		buf->slice.ptr = buf->block->chars;
-		buf->block->len = buf->slice.len;
-	}
-	buf->block->chars[buf->block->len++] = ch;
-	buf->slice.len++;
-}
-
-void bufferPop(struct Buffer *buf) {
-	if (!buf->slice.len) return;
-	buf->slice.len--;
-	buf->block->len--;
-}
-
-wchar_t *bufferDest(struct Buffer *buf, size_t len) {
-	if (buf->block->len + len > buf->cap) {
-		while (len > buf->cap) buf->cap *= 2;
-		buf->block = blockAlloc(buf->block, buf->cap);
-	}
-	wchar_t *ptr = &buf->block->chars[buf->block->len];
-	buf->slice.ptr = ptr;
-	buf->slice.len = len;
-	buf->block->len += len;
-	return ptr;
-}
-
-void bufferTruncate(struct Buffer *buf, size_t len) {
-	if (len > buf->slice.len) return;
-	buf->block->len -= buf->slice.len - len;
-	buf->slice.len = len;
-}
-
-#ifdef TEST
-#include <assert.h>
-
-int main() {
-	struct Buffer buf = bufferAlloc(6);
-
-	bufferSlice(&buf);
-	bufferPush(&buf, L'A');
-	bufferPush(&buf, L'B');
-	assert(!wcsncmp(L"AB", buf.slice.ptr, buf.slice.len));
-
-	bufferSlice(&buf);
-	bufferPush(&buf, L'C');
-	bufferPush(&buf, L'D');
-	assert(!wcsncmp(L"CD", buf.slice.ptr, buf.slice.len));
-
-	bufferSlice(&buf);
-	bufferPush(&buf, L'E');
-	bufferPush(&buf, L'F');
-	bufferPush(&buf, L'G');
-	bufferPush(&buf, L'H');
-	assert(!wcsncmp(L"EFGH", buf.slice.ptr, buf.slice.len));
-
-	bufferFree(&buf);
-
-	buf = bufferAlloc(4);
-	bufferSlice(&buf);
-	bufferPush(&buf, L'A');
-	bufferPush(&buf, L'B');
-	bufferPush(&buf, L'C');
-	bufferPush(&buf, L'D');
-	bufferPush(&buf, L'E');
-	bufferPush(&buf, L'F');
-	assert(!wcsncmp(L"ABCDEF", buf.slice.ptr, buf.slice.len));
-	bufferFree(&buf);
-
-	buf = bufferAlloc(4);
-	bufferSlice(&buf);
-	bufferPush(&buf, L'A');
-	bufferPush(&buf, L'B');
-	bufferPop(&buf);
-	assert(!wcsncmp(L"A", buf.slice.ptr, buf.slice.len));
-	bufferPush(&buf, L'C');
-	assert(!wcsncmp(L"AC", buf.slice.ptr, buf.slice.len));
-	bufferFree(&buf);
-
-	buf = bufferAlloc(4);
-
-	wchar_t *dest = bufferDest(&buf, 2);
-	dest[0] = L'A';
-	dest[1] = L'B';
-	assert(!wcsncmp(L"AB", buf.slice.ptr, buf.slice.len));
-
-	dest = bufferDest(&buf, 3);
-	dest[0] = L'C';
-	dest[1] = L'D';
-	dest[2] = L'E';
-	assert(!wcsncmp(L"CDE", buf.slice.ptr, buf.slice.len));
-
-	bufferFree(&buf);
-
-	buf = bufferAlloc(4);
-	dest = bufferDest(&buf, 6);
-	dest[0] = L'A';
-	dest[1] = L'B';
-	dest[2] = L'C';
-	dest[3] = L'D';
-	dest[4] = L'E';
-	dest[5] = L'F';
-	assert(!wcsncmp(L"ABCDEF", buf.slice.ptr, buf.slice.len));
-	bufferTruncate(&buf, 4);
-	assert(!wcsncmp(L"ABCD", buf.slice.ptr, buf.slice.len));
-	bufferFree(&buf);
-}
-
-#endif
diff --git a/bin/edi/edi.c b/bin/edi/edi.c
deleted file mode 100644
index d234df5e..00000000
--- a/bin/edi/edi.c
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <err.h>
-#include <locale.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sysexits.h>
-
-#include "edi.h"
-
-static void errorExit(enum Error error, const char *prefix) {
-	if (error > Errno) errc(EX_IOERR, error - Errno, "%s", prefix);
-	else errx(EX_DATAERR, "%s: %d", prefix, error);
-}
-
-int main(int argc, char *argv[]) {
-	setlocale(LC_CTYPE, "");
-
-	if (argc < 2) return EX_USAGE;
-
-	enum Error error;
-
-	struct File file = fileAlloc(strdup(argv[1]));
-	error = fileRead(&file);
-	if (error) errorExit(error, file.path);
-
-	FILE *store = fopen("store.edi", "w");
-	if (!store) err(EX_CANTCREAT, "store.edi");
-
-	error = storeWrite(store, &file.edit);
-	if (error) errorExit(error, "store.edi");
-
-	fclose(store);
-	if (ferror(store)) err(EX_IOERR, "store.edi");
-
-	store = fopen("store.edi", "r");
-	if (!store) err(EX_CANTCREAT, "store.edi");
-
-	error = storeRead(store, &file.edit);
-	if (error) errorExit(error, "store.edi");
-
-	const struct Table *table = logTable(&file.edit.log);
-	for (struct Iter it = iter(table, 0); it.ch != WEOF; it = iterNext(it)) {
-		printf("%lc", it.ch);
-	}
-
-	error = fileWrite(&file);
-	if (error) errorExit(error, file.path);
-
-	fileFree(&file);
-}
diff --git a/bin/edi/edi.h b/bin/edi/edi.h
deleted file mode 100644
index 46cb4249..00000000
--- a/bin/edi/edi.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <wchar.h>
-
-struct Span {
-	size_t at, to;
-};
-static inline struct Span spanNext(struct Span span, size_t len) {
-	return (struct Span) { span.to, span.to + len };
-}
-static inline struct Span spanPrev(struct Span span, size_t len) {
-	return (struct Span) { span.at - len, span.at };
-}
-
-struct Slice {
-	const wchar_t *ptr;
-	size_t len;
-};
-
-struct Buffer {
-	size_t cap;
-	struct Slice slice;
-	struct Block {
-		struct Block *prev;
-		size_t len;
-		wchar_t chars[];
-	} *block;
-};
-struct Buffer bufferAlloc(size_t cap);
-void bufferFree(struct Buffer *buf);
-void bufferSlice(struct Buffer *buf);
-void bufferPush(struct Buffer *buf, wchar_t ch);
-void bufferPop(struct Buffer *buf);
-wchar_t *bufferDest(struct Buffer *buf, size_t len);
-void bufferTruncate(struct Buffer *buf, size_t len);
-
-static const struct Table {
-	size_t cap, len;
-	struct Slice *slices;
-} TableEmpty;
-struct Table tableAlloc(size_t cap);
-void tableFree(struct Table *table);
-void tablePush(struct Table *table, struct Slice slice);
-void tableReplace(struct Table *table, struct Slice old, struct Slice new);
-struct Table tableInsert(const struct Table *prev, size_t at, struct Slice ins);
-struct Table tableDelete(const struct Table *prev, struct Span del);
-
-struct Iter {
-	const struct Table *table;
-	struct Span span;
-	size_t slice;
-	size_t at;
-	wint_t ch;
-};
-struct Iter iter(const struct Table *table, size_t at);
-struct Iter iterNext(struct Iter it);
-struct Iter iterPrev(struct Iter it);
-
-struct Log {
-	size_t cap, len;
-	size_t state;
-	struct State {
-		size_t prev, next;
-		struct Table table;
-	} *states;
-};
-struct Log logAlloc(size_t cap);
-void logFree(struct Log *log);
-void logPush(struct Log *log, struct Table table);
-static inline struct Table *logTable(const struct Log *log) {
-	if (log->state == log->len) return NULL;
-	return &log->states[log->state].table;
-}
-
-struct Edit {
-	struct Buffer buf;
-	struct Log log;
-};
-
-enum Error {
-	Ok,
-	StoreMagic,
-	StoreVersion,
-	StoreEOF,
-	FileNoPath,
-	Errno,
-};
-
-enum Error storeWrite(FILE *stream, const struct Edit *edit);
-enum Error storeRead(FILE *stream, struct Edit *edit);
-
-struct File {
-	char *path;
-	size_t clean;
-	struct Edit edit;
-};
-struct File fileAlloc(char *path);
-void fileFree(struct File *file);
-enum Error fileRead(struct File *file);
-enum Error fileWrite(struct File *file);
diff --git a/bin/edi/file.c b/bin/edi/file.c
deleted file mode 100644
index 59d648d2..00000000
--- a/bin/edi/file.c
+++ /dev/null
@@ -1,115 +0,0 @@
-/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <wchar.h>
-
-#include "edi.h"
-
-enum {
-	BufferCap = 8192,
-	TableCap = 2,
-	LogCap = 8,
-};
-
-struct File fileAlloc(char *path) {
-	struct File file = {
-		.path = path,
-		.edit = {
-			.buf = bufferAlloc(BufferCap),
-			.log = logAlloc(LogCap),
-		},
-	};
-	if (!path) logPush(&file.edit.log, TableEmpty);
-	return file;
-}
-
-void fileFree(struct File *file) {
-	logFree(&file->edit.log);
-	bufferFree(&file->edit.buf);
-	free(file->path);
-}
-
-static const mbstate_t StateInit;
-
-enum Error fileRead(struct File *file) {
-	if (!file->path) return FileNoPath;
-
-	FILE *stream = fopen(file->path, "r");
-	if (!stream) {
-		if (errno != ENOENT) return Errno + errno;
-		logPush(&file->edit.log, TableEmpty);
-		file->clean = file->edit.log.state;
-		return Ok;
-	}
-
-	struct Table table = tableAlloc(TableCap);
-	char buf[BufferCap];
-	mbstate_t state = StateInit;
-	while (!feof(stream)) {
-		size_t mbsLen = fread(buf, 1, sizeof(buf), stream);
-		if (ferror(stream)) return Errno + errno;
-
-		// FIXME: Handle null bytes specially.
-		const char *mbs = buf;
-		wchar_t *wcs = bufferDest(&file->edit.buf, mbsLen);
-		size_t wcsLen = mbsnrtowcs(wcs, &mbs, mbsLen, mbsLen, &state);
-		if (wcsLen == (size_t)-1) return Errno + errno;
-
-		bufferTruncate(&file->edit.buf, wcsLen);
-		tablePush(&table, file->edit.buf.slice);
-	}
-	logPush(&file->edit.log, table);
-	file->clean = file->edit.log.state;
-
-	fclose(stream);
-	return Ok;
-}
-
-enum Error fileWrite(struct File *file) {
-	if (!file->path) return FileNoPath;
-
-	FILE *stream = fopen(file->path, "w");
-	if (!stream) return Errno + errno;
-
-	const struct Table *table = logTable(&file->edit.log);
-	assert(table);
-
-	char buf[BufferCap];
-	mbstate_t state = StateInit;
-	for (size_t i = 0; i < table->len; ++i) {
-		struct Slice slice = table->slices[i];
-		while (slice.len) {
-			// FIXME: Handle null bytes specially.
-			size_t mbsLen = wcsnrtombs(
-				buf, &slice.ptr, slice.len, sizeof(buf), &state
-			);
-			if (mbsLen == (size_t)-1) return Errno + errno;
-			// FIXME: This only works once.
-			slice.len -= slice.ptr - table->slices[i].ptr;
-
-			fwrite(buf, 1, mbsLen, stream);
-			if (ferror(stream)) return Errno + errno;
-		}
-	}
-	file->clean = file->edit.log.state;
-
-	fclose(stream);
-	return (ferror(stream) ? Errno + errno : Ok);
-}
diff --git a/bin/edi/iter.c b/bin/edi/iter.c
deleted file mode 100644
index cb34fc2d..00000000
--- a/bin/edi/iter.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdlib.h>
-#include <wchar.h>
-
-#include "edi.h"
-
-struct Iter iter(const struct Table *table, size_t at) {
-	struct Span span = { 0, 0 };
-	size_t slice;
-	for (slice = 0; slice < table->len; ++slice) {
-		span = spanNext(span, table->slices[slice].len);
-		if (span.at <= at && span.to > at) break;
-	}
-	return (struct Iter) {
-		.table = table,
-		.span = span,
-		.slice = slice,
-		.at = (at < span.to ? at : span.to),
-		.ch = (at < span.to ? table->slices[slice].ptr[at - span.at] : WEOF),
-	};
-}
-
-struct Iter iterNext(struct Iter it) {
-	if (it.at == it.span.to && it.ch == WEOF) return it;
-	it.at++;
-	if (it.at == it.span.to) {
-		if (it.slice + 1 == it.table->len) {
-			it.ch = WEOF;
-			return it;
-		}
-		it.slice++;
-		it.span = spanNext(it.span, it.table->slices[it.slice].len);
-	}
-	it.ch = it.table->slices[it.slice].ptr[it.at - it.span.at];
-	return it;
-}
-
-struct Iter iterPrev(struct Iter it) {
-	if (it.at > it.span.to && it.ch == WEOF) return it;
-	it.at--;
-	if (it.at > it.span.to) {
-		it.ch = WEOF;
-		return it;
-	}
-	if (it.at < it.span.at) {
-		it.slice--;
-		it.span = spanPrev(it.span, it.table->slices[it.slice].len);
-	}
-	it.ch = it.table->slices[it.slice].ptr[it.at - it.span.at];
-	return it;
-}
-
-#ifdef TEST
-#include <assert.h>
-
-int main() {
-	struct Slice slices[2] = {
-		{ L"AB", 2 },
-		{ L"CD", 2 },
-	};
-	struct Table table = { .len = 2, .slices = slices };
-
-	assert(L'A' == iter(&table, 0).ch);
-	assert(L'B' == iter(&table, 1).ch);
-	assert(L'C' == iter(&table, 2).ch);
-	assert(L'D' == iter(&table, 3).ch);
-	assert(WEOF == iter(&table, 4).ch);
-
-	assert(L'B' == iterNext(iter(&table, 0)).ch);
-	assert(L'C' == iterNext(iter(&table, 1)).ch);
-	assert(L'D' == iterNext(iter(&table, 2)).ch);
-	assert(WEOF == iterNext(iter(&table, 5)).ch);
-
-	assert(WEOF == iterPrev(iter(&table, 0)).ch);
-	assert(L'A' == iterPrev(iter(&table, 1)).ch);
-	assert(L'B' == iterPrev(iter(&table, 2)).ch);
-	assert(L'C' == iterPrev(iter(&table, 3)).ch);
-
-	struct Iter it = iter(&table, 3);
-	it = iterNext(it);
-	it = iterNext(it);
-	assert(WEOF == it.ch);
-	it = iterPrev(it);
-	assert(L'D' == it.ch);
-
-	it = iter(&table, 0);
-	it = iterPrev(it);
-	it = iterPrev(it);
-	assert(WEOF == it.ch);
-	it = iterNext(it);
-	assert(L'A' == it.ch);
-}
-
-#endif
diff --git a/bin/edi/log.c b/bin/edi/log.c
deleted file mode 100644
index c391bdba..00000000
--- a/bin/edi/log.c
+++ /dev/null
@@ -1,50 +0,0 @@
-/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <assert.h>
-#include <err.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sysexits.h>
-
-#include "edi.h"
-
-struct Log logAlloc(size_t cap) {
-	struct State *states = malloc(sizeof(*states) * cap);
-	if (!states) err(EX_OSERR, "malloc");
-	return (struct Log) { .cap = cap, .states = states };
-}
-
-void logFree(struct Log *log) {
-	for (size_t i = 0; i < log->len; ++i) {
-		tableFree(&log->states[i].table);
-	}
-	free(log->states);
-}
-
-void logPush(struct Log *log, struct Table table) {
-	if (log->len == log->cap) {
-		log->cap *= 2;
-		log->states = realloc(log->states, sizeof(*log->states) * log->cap);
-		if (!log->states) err(EX_OSERR, "realloc");
-	}
-	size_t next = log->len++;
-	log->states[next].table = table;
-	log->states[next].prev = log->state;
-	log->states[next].next = next;
-	log->states[log->state].next = next;
-	log->state = next;
-}
diff --git a/bin/edi/store.c b/bin/edi/store.c
deleted file mode 100644
index 5012eb3e..00000000
--- a/bin/edi/store.c
+++ /dev/null
@@ -1,211 +0,0 @@
-/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <wchar.h>
-
-#include "edi.h"
-
-static const char Magic[3] = "EDI";
-static const char Version  = 1;
-
-static enum Error
-write(FILE *stream, const void *ptr, size_t size, size_t nitems) {
-	size_t n = fwrite(ptr, size, nitems, stream);
-	return (n < nitems ? Errno + errno : Ok);
-}
-
-static enum Error writeBuffer(FILE *stream, const struct Buffer *buf) {
-	size_t len = 0;
-	const struct Block *block;
-	for (block = buf->block; block; block = block->prev) {
-		len += block->len;
-	}
-	enum Error error = write(stream, &len, sizeof(len), 1);
-	if (error) return error;
-	for (block = buf->block; block; block = block->prev) {
-		error = write(stream, block->chars, sizeof(wchar_t), block->len);
-		if (error) return error;
-	}
-	return Ok;
-}
-
-static enum Error
-writeSlice(FILE *stream, const struct Buffer *buf, struct Slice slice) {
-	size_t offset = 0;
-	const struct Block *block = buf->block;
-	while (block) {
-		if (
-			slice.ptr >= block->chars && slice.ptr < &block->chars[block->len]
-		) break;
-		offset += block->len;
-		block = block->prev;
-	}
-	assert(block);
-	size_t ptr = offset + (size_t)(slice.ptr - block->chars);
-	enum Error error = write(stream, &ptr, sizeof(ptr), 1);
-	if (error) return error;
-	return write(stream, &slice.len, sizeof(slice.len), 1);
-}
-
-static enum Error
-writeTable(FILE *stream, const struct Buffer *buf, const struct Table *table) {
-	enum Error error = write(stream, &table->len, sizeof(table->len), 1);
-	for (size_t i = 0; i < table->len; ++i) {
-		error = writeSlice(stream, buf, table->slices[i]);
-		if (error) return error;
-	}
-	return Ok;
-}
-
-static enum Error
-writeState(FILE *stream, const struct Buffer *buf, const struct State *state) {
-	enum Error error;
-	error = write(stream, &state->prev, sizeof(state->prev), 1);
-	if (error) return error;
-	error = write(stream, &state->next, sizeof(state->next), 1);
-	if (error) return error;
-	return writeTable(stream, buf, &state->table);
-}
-
-static enum Error
-writeLog(FILE *stream, const struct Buffer *buf, const struct Log *log) {
-	enum Error error;
-	error = write(stream, &log->state, sizeof(log->state), 1);
-	if (error) return error;
-	error = write(stream, &log->len, sizeof(log->len), 1);
-	if (error) return error;
-	for (size_t i = 0; i < log->len; ++i) {
-		error = writeState(stream, buf, &log->states[i]);
-		if (error) return error;
-	}
-	return Ok;
-}
-
-enum Error storeWrite(FILE *stream, const struct Edit *edit) {
-	enum Error error;
-	error = write(stream, Magic, sizeof(Magic), 1);
-	if (error) return error;
-	error = write(stream, &Version, sizeof(Version), 1);
-	if (error) return error;
-	error = writeBuffer(stream, &edit->buf);
-	if (error) return error;
-	return writeLog(stream, &edit->buf, &edit->log);
-}
-
-static enum Error read(FILE *stream, void *ptr, size_t size, size_t nitems) {
-	size_t n = fread(ptr, size, nitems, stream);
-	if (ferror(stream)) return Errno + errno;
-	return (n < nitems ? StoreEOF : Ok);
-}
-
-static enum Error readMagic(FILE *stream) {
-	char magic[3];
-	enum Error error = read(stream, magic, sizeof(magic), 1);
-	if (error) return error;
-	return (
-		magic[0] != Magic[0] || magic[1] != Magic[1] || magic[2] != Magic[2]
-		? StoreMagic
-		: Ok
-	);
-}
-
-static enum Error readVersion(FILE *stream) {
-	char version;
-	enum Error error = read(stream, &version, sizeof(version), 1);
-	if (error) return error;
-	return (version != Version ? StoreVersion : Ok);
-}
-
-static enum Error readBuffer(FILE *stream, struct Buffer *buf) {
-	size_t len;
-	enum Error error = read(stream, &len, sizeof(len), 1);
-	if (error) return error;
-	wchar_t *dest = bufferDest(buf, len);
-	return read(stream, dest, sizeof(wchar_t), len);
-}
-
-static enum Error
-readSlice(FILE *stream, struct Buffer *buf, struct Table *table) {
-	enum Error error;
-	size_t ptr, len;
-	error = read(stream, &ptr, sizeof(ptr), 1);
-	if (error) return error;
-	error = read(stream, &len, sizeof(len), 1);
-	if (error) return error;
-	tablePush(table, (struct Slice) { &buf->slice.ptr[ptr], len });
-	return Ok;
-}
-
-static enum Error
-readTable(FILE *stream, struct Buffer *buf, struct Table *table) {
-	size_t len;
-	enum Error error = read(stream, &len, sizeof(len), 1);
-	if (error) return error;
-	*table = tableAlloc(len);
-	for (size_t i = 0; i < len; ++i) {
-		error = readSlice(stream, buf, table);
-		if (error) return error;
-	}
-	return Ok;
-}
-
-static enum Error
-readState(FILE *stream, struct Buffer *buf, size_t offset, struct Log *log) {
-	enum Error error;
-	size_t prev, next;
-	struct Table table;
-	error = read(stream, &prev, sizeof(prev), 1);
-	if (error) return error;
-	error = read(stream, &next, sizeof(next), 1);
-	if (error) return error;
-	error = readTable(stream, buf, &table);
-	if (error) return error;
-	logPush(log, table);
-	log->states[log->state].prev = offset + prev;
-	log->states[log->state].next = offset + next;
-	return Ok;
-}
-
-static enum Error readLog(FILE *stream, struct Buffer *buf, struct Log *log) {
-	enum Error error;
-	size_t state, len;
-	error = read(stream, &state, sizeof(state), 1);
-	if (error) return error;
-	error = read(stream, &len, sizeof(len), 1);
-	if (error) return error;
-	size_t offset = log->len;
-	for (size_t i = 0; i < len; ++i) {
-		error = readState(stream, buf, offset, log);
-		if (error) return error;
-	}
-	log->state = offset + state;
-	return Ok;
-}
-
-enum Error storeRead(FILE *stream, struct Edit *edit) {
-	enum Error error;
-	error = readMagic(stream);
-	if (error) return error;
-	error = readVersion(stream);
-	if (error) return error;
-	error = readBuffer(stream, &edit->buf);
-	if (error) return error;
-	return readLog(stream, &edit->buf, &edit->log);
-}
diff --git a/bin/edi/table.c b/bin/edi/table.c
deleted file mode 100644
index 6540581e..00000000
--- a/bin/edi/table.c
+++ /dev/null
@@ -1,171 +0,0 @@
-/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <err.h>
-#include <stdlib.h>
-#include <sysexits.h>
-#include <wchar.h>
-
-#include "edi.h"
-
-struct Table tableAlloc(size_t cap) {
-	struct Slice *slices = malloc(sizeof(*slices) * cap);
-	if (!slices) err(EX_OSERR, "malloc");
-	return (struct Table) { .cap = cap, .slices = slices };
-}
-
-void tableFree(struct Table *table) {
-	free(table->slices);
-}
-
-void tablePush(struct Table *table, struct Slice slice) {
-	if (table->len == table->cap) {
-		table->cap *= 2;
-		table->slices = realloc(
-			table->slices,
-			sizeof(*table->slices) * table->cap
-		);
-		if (!table->slices) err(EX_OSERR, "malloc");
-	}
-	table->slices[table->len++] = slice;
-}
-
-void tableReplace(struct Table *table, struct Slice old, struct Slice new) {
-	for (size_t i = 0; i < table->len; ++i) {
-		if (table->slices[i].ptr != old.ptr) continue;
-		if (table->slices[i].len != old.len) continue;
-		table->slices[i] = new;
-		break;
-	}
-}
-
-struct Table tableInsert(const struct Table *prev, size_t at, struct Slice ins) {
-	struct Table next = tableAlloc(prev->len + 2);
-	struct Span span = { 0, 0 };
-	for (size_t i = 0; i < prev->len; ++i) {
-		span = spanNext(span, prev->slices[i].len);
-		if (span.at == at) {
-			next.slices[next.len++] = ins;
-			next.slices[next.len++] = prev->slices[i];
-		} else if (span.at < at && span.to > at) {
-			next.slices[next.len++] = (struct Slice) {
-				prev->slices[i].ptr,
-				at - span.at,
-			};
-			next.slices[next.len++] = ins;
-			next.slices[next.len++] = (struct Slice) {
-				&prev->slices[i].ptr[at - span.at],
-				prev->slices[i].len - (at - span.at),
-			};
-		} else {
-			next.slices[next.len++] = prev->slices[i];
-		}
-	}
-	if (span.to == at) {
-		next.slices[next.len++] = ins;
-	}
-	return next;
-}
-
-struct Table tableDelete(const struct Table *prev, struct Span del) {
-	struct Table next = tableAlloc(prev->len + 1);
-	struct Span span = { 0, 0 };
-	for (size_t i = 0; i < prev->len; ++i) {
-		span = spanNext(span, prev->slices[i].len);
-		if (span.at >= del.at && span.to <= del.to) {
-			(void)prev->slices[i];
-		} else if (span.at < del.at && span.to > del.to) {
-			next.slices[next.len++] = (struct Slice) {
-				prev->slices[i].ptr,
-				del.at - span.at,
-			};
-			next.slices[next.len++] = (struct Slice) {
-				&prev->slices[i].ptr[del.to - span.at],
-				prev->slices[i].len - (del.to - span.at),
-			};
-		} else if (span.at < del.at && span.to > del.at) {
-			next.slices[next.len++] = (struct Slice) {
-				prev->slices[i].ptr,
-				del.at - span.at,
-			};
-		} else if (span.at < del.to && span.to > del.to) {
-			next.slices[next.len++] = (struct Slice) {
-				&prev->slices[i].ptr[del.to - span.at],
-				prev->slices[i].len - (del.to - span.at),
-			};
-		} else {
-			next.slices[next.len++] = prev->slices[i];
-		}
-	}
-	return next;
-}
-
-#ifdef TEST
-#include <assert.h>
-
-static struct Slice slice(const wchar_t *str) {
-	return (struct Slice) { str, wcslen(str) };
-}
-
-static struct Span span(size_t at, size_t to) {
-	return (struct Span) { at, to };
-}
-
-static int eq(const wchar_t *str, const struct Table *table) {
-	for (size_t i = 0; i < table->len; ++i) {
-		if (wcsncmp(str, table->slices[i].ptr, table->slices[i].len)) {
-			return 0;
-		}
-		str = &str[table->slices[i].len];
-	}
-	return !str[0];
-}
-
-int main() {
-	struct Table abc = tableInsert(&TableEmpty, 0, slice(L"ABC"));
-	assert(eq(L"ABC", &abc));
-
-	struct Table dabc = tableInsert(&abc, 0, slice(L"D"));
-	struct Table abcd = tableInsert(&abc, 3, slice(L"D"));
-	struct Table adbc = tableInsert(&abc, 1, slice(L"D"));
-
-	assert(eq(L"DABC", &dabc));
-	assert(eq(L"ABCD", &abcd));
-	assert(eq(L"ADBC", &adbc));
-
-	tableFree(&dabc);
-	tableFree(&abcd);
-	tableFree(&adbc);
-
-	struct Table bc = tableDelete(&abc, span(0, 1));
-	struct Table ab = tableDelete(&abc, span(2, 3));
-	struct Table ac = tableDelete(&abc, span(1, 2));
-	struct Table __ = tableDelete(&abc, span(0, 3));
-
-	assert(eq(L"BC", &bc));
-	assert(eq(L"AB", &ab));
-	assert(eq(L"AC", &ac));
-	assert(eq(L"",   &__));
-
-	tableFree(&bc);
-	tableFree(&ab);
-	tableFree(&ac);
-	tableFree(&__);
-
-	tableFree(&abc);
-}
-
-#endif