summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLouis Burda <quent.burda@gmail.com>2024-10-02 03:59:21 +0200
committerLouis Burda <quent.burda@gmail.com>2024-10-02 03:59:21 +0200
commit36c8d24c07ef3c6658f397fd156396f83b35ac27 (patch)
tree1b3293264dca677a8d8e1ca44061aa7af191f839
parenteff49100e4fca394a23973abd5001a4fde15d6e8 (diff)
downloadtabular-master.tar.gz
tabular-master.zip
Vendor subgit modulesHEADmaster
-rw-r--r--.gitignore4
-rw-r--r--.gitmodules12
-rw-r--r--.subgitrc24
-rw-r--r--build.rmk43
-rw-r--r--build.rmk.tmpl (renamed from build.jst.tmpl)18
-rwxr-xr-xconfigure5
m---------lib/liballoc0
-rw-r--r--lib/liballoc/.gitignore7
-rw-r--r--lib/liballoc/LICENSE21
-rw-r--r--lib/liballoc/Makefile45
-rw-r--r--lib/liballoc/build.rmk.tmpl60
-rw-r--r--lib/liballoc/common.mk9
-rwxr-xr-xlib/liballoc/configure5
-rw-r--r--lib/liballoc/include/allocator.h22
-rw-r--r--lib/liballoc/liballoc.api4
-rw-r--r--lib/liballoc/liballoc.lds7
-rw-r--r--lib/liballoc/src/allocator.c150
-rw-r--r--lib/liballoc/src/test.c22
m---------lib/libdvec0
-rw-r--r--lib/libdvec/.gitignore9
-rw-r--r--lib/libdvec/.gitmodules0
-rw-r--r--lib/libdvec/.subgitrc9
-rw-r--r--lib/libdvec/LICENSE21
-rw-r--r--lib/libdvec/Makefile59
-rw-r--r--lib/libdvec/build.rmk.tmpl69
-rwxr-xr-xlib/libdvec/configure8
-rw-r--r--lib/libdvec/include/dvec.h182
-rw-r--r--lib/libdvec/lib/liballoc/.gitignore7
-rw-r--r--lib/libdvec/lib/liballoc/LICENSE21
-rw-r--r--lib/libdvec/lib/liballoc/Makefile45
-rw-r--r--lib/libdvec/lib/liballoc/build.jst.tmpl60
-rw-r--r--lib/libdvec/lib/liballoc/common.mk9
-rwxr-xr-xlib/libdvec/lib/liballoc/configure3
-rw-r--r--lib/libdvec/lib/liballoc/include/allocator.h22
-rw-r--r--lib/libdvec/lib/liballoc/liballoc.api4
-rw-r--r--lib/libdvec/lib/liballoc/liballoc.lds7
-rw-r--r--lib/libdvec/lib/liballoc/src/allocator.c150
-rw-r--r--lib/libdvec/lib/liballoc/src/test.c22
-rw-r--r--lib/libdvec/libdvec.api25
-rw-r--r--lib/libdvec/libdvec.lds29
-rw-r--r--lib/libdvec/src/dvec.c510
-rw-r--r--lib/libdvec/src/test.c77
m---------lib/libhmap0
-rw-r--r--lib/libhmap/.gitignore7
-rw-r--r--lib/libhmap/.subgitrc9
-rw-r--r--lib/libhmap/LICENSE21
-rw-r--r--lib/libhmap/Makefile59
-rw-r--r--lib/libhmap/build.rmk.tmpl69
-rwxr-xr-xlib/libhmap/configure8
-rw-r--r--lib/libhmap/include/hmap.h123
-rw-r--r--lib/libhmap/lib/liballoc/.gitignore7
-rw-r--r--lib/libhmap/lib/liballoc/LICENSE21
-rw-r--r--lib/libhmap/lib/liballoc/Makefile45
-rw-r--r--lib/libhmap/lib/liballoc/build.jst.tmpl60
-rw-r--r--lib/libhmap/lib/liballoc/common.mk9
-rwxr-xr-xlib/libhmap/lib/liballoc/configure3
-rw-r--r--lib/libhmap/lib/liballoc/include/allocator.h22
-rw-r--r--lib/libhmap/lib/liballoc/liballoc.api4
-rw-r--r--lib/libhmap/lib/liballoc/liballoc.lds7
-rw-r--r--lib/libhmap/lib/liballoc/src/allocator.c150
-rw-r--r--lib/libhmap/lib/liballoc/src/test.c22
-rw-r--r--lib/libhmap/libhmap.api25
-rw-r--r--lib/libhmap/libhmap.lds27
-rw-r--r--lib/libhmap/src/hmap.c320
-rw-r--r--lib/libhmap/src/test.c42
m---------lib/libtabular0
-rw-r--r--lib/libtabular/.gitignore8
-rw-r--r--lib/libtabular/.gitmodules0
-rw-r--r--lib/libtabular/.subgitrc9
-rw-r--r--lib/libtabular/build.jst0
-rw-r--r--lib/libtabular/build.rmk.tmpl70
-rwxr-xr-xlib/libtabular/configure8
-rw-r--r--lib/libtabular/include/tabular.h133
-rw-r--r--lib/libtabular/lib/liballoc/.gitignore7
-rw-r--r--lib/libtabular/lib/liballoc/LICENSE21
-rw-r--r--lib/libtabular/lib/liballoc/Makefile45
-rw-r--r--lib/libtabular/lib/liballoc/build.jst51
-rw-r--r--lib/libtabular/lib/liballoc/build.rmk.tmpl60
-rw-r--r--lib/libtabular/lib/liballoc/common.mk9
-rwxr-xr-xlib/libtabular/lib/liballoc/configure5
-rw-r--r--lib/libtabular/lib/liballoc/include/allocator.h22
-rw-r--r--lib/libtabular/lib/liballoc/liballoc.api4
-rw-r--r--lib/libtabular/lib/liballoc/liballoc.lds7
-rw-r--r--lib/libtabular/lib/liballoc/src/allocator.c150
-rw-r--r--lib/libtabular/lib/liballoc/src/test.c22
-rw-r--r--lib/libtabular/libtabular.api7
-rw-r--r--lib/libtabular/libtabular.lds7
-rw-r--r--lib/libtabular/src/tabular.c695
-rw-r--r--lib/libtabular/src/test.c154
-rw-r--r--lib/libtabular/src/util.c146
-rw-r--r--lib/libtabular/src/util.h25
91 files changed, 4508 insertions, 23 deletions
diff --git a/.gitignore b/.gitignore
index 9c71603..ab8eba0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
.cache
.gdb_history
compile_commands.json
-build.jst
+build.rmk
tabular
+.subgit
+/.subgit
diff --git a/.gitmodules b/.gitmodules
index ee1b0f1..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,12 +0,0 @@
-[submodule "lib/libtabular"]
- path = lib/libtabular
- url = git@sinitax.com:sinitax/libtabular-c
-[submodule "lib/libdvec"]
- path = lib/libdvec
- url = git@sinitax.com:sinitax/libdvec-c
-[submodule "lib/libhmap"]
- path = lib/libhmap
- url = git@sinitax.com:sinitax/libhmap-c
-[submodule "lib/liballoc"]
- path = lib/liballoc
- url = git@sinitax.com:sinitax/liballoc-c
diff --git a/.subgitrc b/.subgitrc
new file mode 100644
index 0000000..2b162b1
--- /dev/null
+++ b/.subgitrc
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+declare -A subgit subgitinfo
+
+subgit[lib/libdvec]=1
+subgitinfo[lib/libdvec/remote]='git@sinitax.com:sinitax/libdvec-c'
+subgitinfo[lib/libdvec/branch]='master'
+subgitinfo[lib/libdvec/commit]='83827177907caedf60f8c1c8d1692d976dcef3ee'
+
+subgit[lib/liballoc]=1
+subgitinfo[lib/liballoc/remote]='git@sinitax.com:sinitax/liballoc-c'
+subgitinfo[lib/liballoc/branch]='master'
+subgitinfo[lib/liballoc/commit]='fd51196ac2d1bf1c34c8578146ef1bc66c1c8ec4'
+
+subgit[lib/libtabular]=1
+subgitinfo[lib/libtabular/remote]='git@sinitax.com:sinitax/libtabular-c'
+subgitinfo[lib/libtabular/branch]='master'
+subgitinfo[lib/libtabular/commit]='897ff51fdb9bccb90415d1bcd54541640dba4465'
+
+subgit[lib/libhmap]=1
+subgitinfo[lib/libhmap/remote]='git@sinitax.com:sinitax/libhmap-c'
+subgitinfo[lib/libhmap/branch]='master'
+subgitinfo[lib/libhmap/commit]='eff6ad090ed53cf97b11393fc3ab45abe8f67214'
+
diff --git a/build.rmk b/build.rmk
new file mode 100644
index 0000000..f794a51
--- /dev/null
+++ b/build.rmk
@@ -0,0 +1,43 @@
+
+
+cflags = -I lib/libdvec/include -I lib/libhmap/include
+ -I lib/libtabular/include -I lib/liballoc/include
+ -Wunused-variable -Wunused-function -Wconversion
+ -O2
+
+rule cc
+ gcc -o $out $in $cflags
+
+target lib/libdvec/build/libdvec.a
+ rmk lib/libdvec
+
+target lib/libhmap/build/libhmap.a
+ rmk lib/libhmap
+
+target lib/libtabular/build/libtabular.a
+ rmk lib/libtabular
+
+target lib/liballoc/build/liballoc.a
+ rmk lib/liballoc
+
+target tabular
+ cc tabular.c lib/libdvec/build/libdvec.a lib/libhmap/build/libhmap.a
+ lib/libtabular/build/libtabular.a lib/liballoc/build/liballoc.a
+
+command clean
+ rm -f tabular
+
+command cleanall
+ rmk clean
+ rmk -C lib/libtabular clean
+ rmk -C lib/libhmap clean
+ rmk -C lib/libdvec clean
+
+command install
+ install -m755 tabular -t "/usr/local/bin"
+
+command uninstall
+ rm -f "/usr/local/bin/tabular"
+
+command all
+ rmk tabular
diff --git a/build.jst.tmpl b/build.rmk.tmpl
index 45eb28a..c51085e 100644
--- a/build.jst.tmpl
+++ b/build.rmk.tmpl
@@ -17,16 +17,16 @@ rule cc
#{CC} -o $out $in $cflags
target lib/libdvec/build/libdvec.a
- just lib/libdvec
+ rmk lib/libdvec
target lib/libhmap/build/libhmap.a
- just lib/libhmap
+ rmk lib/libhmap
target lib/libtabular/build/libtabular.a
- just lib/libtabular
+ rmk lib/libtabular
target lib/liballoc/build/liballoc.a
- just lib/liballoc
+ rmk lib/liballoc
target tabular
cc tabular.c lib/libdvec/build/libdvec.a lib/libhmap/build/libhmap.a
@@ -36,10 +36,10 @@ command clean
rm -f tabular
command cleanall
- just clean
- just -C lib/libtabular clean
- just -C lib/libhmap clean
- just -C lib/libdvec clean
+ rmk clean
+ rmk -C lib/libtabular clean
+ rmk -C lib/libhmap clean
+ rmk -C lib/libdvec clean
command install
install -m755 tabular -t "#{DESTDIR}#{PREFIX}#{BINDIR}"
@@ -48,4 +48,4 @@ command uninstall
rm -f "#{DESTDIR}#{PREFIX}#{BINDIR}/tabular"
command all
- just tabular
+ rmk tabular
diff --git a/configure b/configure
index a993cba..bf50ac0 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,9 @@
#!/bin/sh
-tmpl "$@" build.jst.tmpl > build.jst
+set -ex
+
+tmpl "$@" build.rmk.tmpl > build.rmk
+
for lib in ./lib/*; do
pushd $lib
./configure "$@"
diff --git a/lib/liballoc b/lib/liballoc
deleted file mode 160000
-Subproject 3f388a2659ae2d121322101930d33412815d84e
diff --git a/lib/liballoc/.gitignore b/lib/liballoc/.gitignore
new file mode 100644
index 0000000..b6a670a
--- /dev/null
+++ b/lib/liballoc/.gitignore
@@ -0,0 +1,7 @@
+compile_commands.json
+build
+build.rmk
+.cache
+vgcore*
+.gdb_history
+test
diff --git a/lib/liballoc/LICENSE b/lib/liballoc/LICENSE
new file mode 100644
index 0000000..361f116
--- /dev/null
+++ b/lib/liballoc/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Louis Burda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/liballoc/Makefile b/lib/liballoc/Makefile
new file mode 100644
index 0000000..dab8818
--- /dev/null
+++ b/lib/liballoc/Makefile
@@ -0,0 +1,45 @@
+PREFIX ?= /usr/local
+LIBDIR ?= /lib
+INCLDIR ?= /include
+
+CFLAGS = -I include
+
+ifeq "$(DEBUG)" "1"
+CFLAGS += -Og -g
+else
+CFLAGS += -O2
+endif
+
+all: build/liballoc.so build/liballoc.a build/test
+
+clean:
+ rm -rf build
+
+cleanall: clean
+
+build:
+ mkdir build
+
+build/liballoc.a: src/allocator.c | build
+ $(CC) -o build/tmp.o src/allocator.c $(CFLAGS) -r
+ objcopy --keep-global-symbols=liballoc.api build/tmp.o build/fixed.o
+ ar rcs $@ build/fixed.o
+
+build/liballoc.so: src/allocator.c include/allocator.h | build
+ $(CC) -o $@ src/allocator.c $(CFLAGS) \
+ -shared -Wl,-version-script liballoc.lds
+
+build/test: src/test.c build/liballoc.a | build
+ $(CC) -o $@ $^ -I include
+
+install:
+ install -m755 include/allocator.h -t "$(DESTDIR)$(PREFIX)$(INCLDIR)"
+ install -m755 build/liballoc.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+ install -m755 build/liballoc.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)$(INCLDIR)/allocator.h"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.a"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.so"
+
+.PHONY: all clean cleanall install uninstall
diff --git a/lib/liballoc/build.rmk.tmpl b/lib/liballoc/build.rmk.tmpl
new file mode 100644
index 0000000..410749d
--- /dev/null
+++ b/lib/liballoc/build.rmk.tmpl
@@ -0,0 +1,60 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include #{OPT_CFLAGS} #{EXTRA_CFLAGS}
+
+rule liba
+ gcc -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=liballoc.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ gcc -o $out $in $cflags -shared -Wl,-version-script liballoc.lds
+
+rule cc
+ gcc -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target build/liballoc.a
+ liba src/allocator.c | include/allocator.h build
+
+target build/liballoc.so
+ libso src/allocator.c | include/allocator.h build
+
+target build/test
+ cc src/test.c build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ just clean
+
+command install
+ install -m755 build/liballoc.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/liballoc.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m644 include/allocator.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.so"
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/allocator.h"
+
+command all
+ just build/liballoc.a build/liballoc.so build/test
+
diff --git a/lib/liballoc/common.mk b/lib/liballoc/common.mk
new file mode 100644
index 0000000..fe00d79
--- /dev/null
+++ b/lib/liballoc/common.mk
@@ -0,0 +1,9 @@
+LIBALLOC_A = build/liballoc.a
+LIBALLOC_A_SRC = src/allocator.c
+LIBALLOC_A_DEP = $(LIBALLOC_A_SRC) include/allocator.h
+LIBALLOC_A_SRCDEP = $(LIBALLOC_A_DEP)
+
+LIBALLOC_SO = build/liballoc.so
+LIBALLOC_SO_SRC = src/allocator.c
+LIBALLOC_SO_DEP = $(LIBALLOC_SO_SRC) include/allocator.h
+LIBALLOC_SO_SRCDEP = $(LIBALLOC_SO_DEP)
diff --git a/lib/liballoc/configure b/lib/liballoc/configure
new file mode 100755
index 0000000..16d5220
--- /dev/null
+++ b/lib/liballoc/configure
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -ex
+
+tmpl "$@" build.rmk.tmpl > build.rmk
diff --git a/lib/liballoc/include/allocator.h b/lib/liballoc/include/allocator.h
new file mode 100644
index 0000000..fc9a13d
--- /dev/null
+++ b/lib/liballoc/include/allocator.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <stddef.h>
+
+struct allocator {
+ void *(*alloc)(const struct allocator *allocator,
+ size_t size, int *rc);
+ void *(*realloc)(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+ int (*free)(const struct allocator *allocator, void *p);
+};
+
+struct strict_allocator {
+ const struct allocator *allocator_ul;
+ struct allocator allocator;
+};
+
+void strict_allocator_init(struct strict_allocator *strict_allocator_init,
+ const struct allocator *allocator_ul);
+
+extern const struct allocator stdlib_heap_allocator;
+extern const struct allocator stdlib_strict_heap_allocator;
diff --git a/lib/liballoc/liballoc.api b/lib/liballoc/liballoc.api
new file mode 100644
index 0000000..e875b2b
--- /dev/null
+++ b/lib/liballoc/liballoc.api
@@ -0,0 +1,4 @@
+strict_allocator_init
+
+stdlib_heap_allocator
+stdlib_strict_heap_allocator
diff --git a/lib/liballoc/liballoc.lds b/lib/liballoc/liballoc.lds
new file mode 100644
index 0000000..07e5fca
--- /dev/null
+++ b/lib/liballoc/liballoc.lds
@@ -0,0 +1,7 @@
+LIBALLOC_2.1 {
+ global:
+ strict_allocator_init;
+ stdlib_heap_allocator;
+ stdlib_strict_heap_allocator;
+ local: *;
+};
diff --git a/lib/liballoc/src/allocator.c b/lib/liballoc/src/allocator.c
new file mode 100644
index 0000000..eb6bc6d
--- /dev/null
+++ b/lib/liballoc/src/allocator.c
@@ -0,0 +1,150 @@
+#include "allocator.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+static void *stdlib_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_heap_free(const struct allocator *allocator, void *p);
+
+static void *stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_strict_heap_free(const struct allocator *allocator, void *p);
+
+const struct allocator stdlib_heap_allocator = {
+ .alloc = stdlib_heap_alloc,
+ .realloc = stdlib_heap_realloc,
+ .free = stdlib_heap_free
+};
+
+const struct allocator stdlib_strict_heap_allocator = {
+ .alloc = stdlib_strict_heap_alloc,
+ .realloc = stdlib_strict_heap_realloc,
+ .free = stdlib_strict_heap_free
+};
+
+void *
+stdlib_heap_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (rc && !p) *rc = errno;
+
+ return p;
+}
+
+void *
+stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (rc && !np) *rc = errno;
+
+ return np;
+}
+
+int
+stdlib_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+stdlib_strict_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+strict_allocator_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *p;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ p = strict_allocator->allocator_ul->alloc(
+ strict_allocator->allocator_ul, size, rc);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+strict_allocator_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *np;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ np = strict_allocator->allocator_ul->realloc(
+ strict_allocator->allocator_ul, p, size, rc);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+strict_allocator_free(const struct allocator *allocator, void *p)
+{
+ const struct strict_allocator *strict_allocator;
+ int rc;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ rc = strict_allocator->allocator_ul->free(
+ strict_allocator->allocator_ul, p);
+ if (rc) abort();
+
+ return 0;
+}
+
+void
+strict_allocator_init(struct strict_allocator *strict_allocator,
+ const struct allocator *allocator_ul)
+{
+ strict_allocator->allocator_ul = allocator_ul;
+ strict_allocator->allocator.alloc = strict_allocator_alloc;
+ strict_allocator->allocator.realloc = strict_allocator_realloc;
+ strict_allocator->allocator.free = strict_allocator_free;
+}
diff --git a/lib/liballoc/src/test.c b/lib/liballoc/src/test.c
new file mode 100644
index 0000000..7b3ee44
--- /dev/null
+++ b/lib/liballoc/src/test.c
@@ -0,0 +1,22 @@
+#include "allocator.h"
+
+#include <err.h>
+#include <string.h>
+#include <stdlib.h>
+
+const struct allocator *ga = &stdlib_heap_allocator;
+
+int
+main(int argc, const char **argv)
+{
+ struct test *test;
+ int rc;
+
+ if (argc <= 1) exit(1);
+
+ test = ga->alloc(ga, strtoull(argv[1], NULL, 10), &rc);
+ if (!test) errx(1, "alloc: %s", strerror(rc));
+
+ rc = ga->free(ga, test);
+ if (rc) errx(1, "free: %s", strerror(rc));
+}
diff --git a/lib/libdvec b/lib/libdvec
deleted file mode 160000
-Subproject d9bc4e34e3a880e31118af3b7f623d644af7025
diff --git a/lib/libdvec/.gitignore b/lib/libdvec/.gitignore
new file mode 100644
index 0000000..374a2c2
--- /dev/null
+++ b/lib/libdvec/.gitignore
@@ -0,0 +1,9 @@
+compile_commands.json
+build
+build.rmk
+.cache
+vgcore*
+test
+.gdb_history
+/.subgit
+/.subrepo
diff --git a/lib/libdvec/.gitmodules b/lib/libdvec/.gitmodules
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/libdvec/.gitmodules
diff --git a/lib/libdvec/.subgitrc b/lib/libdvec/.subgitrc
new file mode 100644
index 0000000..5f1b14f
--- /dev/null
+++ b/lib/libdvec/.subgitrc
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+declare -A subgit subgitinfo
+
+subgit[lib/liballoc]=1
+subgitinfo[lib/liballoc/remote]='git@sinitax.com:sinitax/liballoc-c'
+subgitinfo[lib/liballoc/branch]='master'
+subgitinfo[lib/liballoc/commit]='24d37e4298575df36399f32a6fa5ef10b005c964'
+
diff --git a/lib/libdvec/LICENSE b/lib/libdvec/LICENSE
new file mode 100644
index 0000000..361f116
--- /dev/null
+++ b/lib/libdvec/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Louis Burda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/libdvec/Makefile b/lib/libdvec/Makefile
new file mode 100644
index 0000000..0361af4
--- /dev/null
+++ b/lib/libdvec/Makefile
@@ -0,0 +1,59 @@
+PREFIX ?= /usr/local
+LIBDIR ?= /lib
+INCLDIR ?= /include
+
+CFLAGS = -I include -I lib/liballoc/include
+CFLAGS += -Wunused-function -Wunused-variable -Wno-prototype
+CFLAGS += -Wconversion -Wsign-compare -Werror
+
+ifeq "$(DEBUG)" "1"
+CFLAGS += -Og -g
+else
+CFLAGS += -O2
+endif
+
+ifeq "$(ASSERT_ARGS)" "1"
+CFLAGS += -DLIBDVEC_ASSERT_ARGS=1
+endif
+
+ifeq "$(ASSERT_ALLOC)" "1"
+CFLAGS += -DLIBDVEC_ASSERT_ALLOC=1
+endif
+
+all: build/libdvec.so build/libdvec.a build/test
+
+clean:
+ rm -rf build
+
+cleanall: clean
+ make -C lib/liballoc cleanall
+
+build:
+ mkdir build
+
+lib/liballoc/build/liballoc.a:
+ make -C lib/liballoc build/liballoc.a
+
+build/libdvec.a: src/dvec.c include/dvec.h libdvec.api | build
+ $(CC) -o build/tmp.o src/dvec.c $(CFLAGS) -r
+ objcopy --keep-global-symbols=libdvec.api build/tmp.o build/fixed.o
+ ar rcs $@ build/fixed.o
+
+build/libdvec.so: src/dvec.c include/dvec.h libdvec.lds | build
+ $(CC) -o $@ src/dvec.c -fPIC $(CFLAGS) \
+ -shared -Wl,-version-script libdvec.lds
+
+build/test: src/test.c build/libdvec.a lib/liballoc/build/liballoc.a
+ $(CC) -o $@ $^ $(CFLAGS)
+
+install:
+ install -m644 include/dvec.h -t "$(DESTDIR)$(PREFIX)$(INCLDIR)"
+ install -m755 build/libdvec.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+ install -m755 build/libdvec.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)$(INCLDIR)/dvec.h"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/libdvec.so"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/libdvec.a"
+
+.PHONY: all clean cleanall install uninstall
diff --git a/lib/libdvec/build.rmk.tmpl b/lib/libdvec/build.rmk.tmpl
new file mode 100644
index 0000000..873a83d
--- /dev/null
+++ b/lib/libdvec/build.rmk.tmpl
@@ -0,0 +1,69 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef LIBDVEC_DEBUG
+#define DEBUG 1
+#endif
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -Wpedantic -I include -I lib/liballoc/include
+ #{OPT_CFLAGS} #{LIBDVEC_EXTRA_CFLAGS} #{EXTRA_CFLAGS}
+
+rule liba
+ #{CC} -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=libdvec.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ #{CC} -o $out $in $cflags -shared -Wl,-version-script libdvec.lds
+
+rule cc
+ #{CC} -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target lib/liballoc/build/liballoc.a
+ rmk lib/liballoc
+
+target build/libdvec.a
+ liba src/dvec.c | include/dvec.h build
+
+target build/libdvec.so
+ libso src/dvec.c | include/dvec.h build
+
+target build/test
+ cc src/test.c build/libdvec.a lib/liballoc/build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ rmk clean
+ rmk -C lib/liballoc cleanall
+
+command install
+ install -m755 build/libdvec.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/libdvec.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m644 include/dvec.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libdvec.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libdvec.so"
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/dvec.h"
+
+command all
+ rmk build/libdvec.a build/libdvec.so build/test
+
diff --git a/lib/libdvec/configure b/lib/libdvec/configure
new file mode 100755
index 0000000..b4dd05d
--- /dev/null
+++ b/lib/libdvec/configure
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+tmpl "$@" build.rmk.tmpl > build.rmk
+for lib in ./lib/*; do
+ pushd $lib
+ ./configure "$@"
+ popd
+done
diff --git a/lib/libdvec/include/dvec.h b/lib/libdvec/include/dvec.h
new file mode 100644
index 0000000..45888a6
--- /dev/null
+++ b/lib/libdvec/include/dvec.h
@@ -0,0 +1,182 @@
+#pragma once
+
+#include "allocator.h"
+
+#include <sys/types.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#define DVEC_ERRNO(rc) ((rc) > 0 && (rc) & (1 << 30) ? (rc) & ~(1 << 30) : 0)
+#define DVEC_ERR(lut, rc) errx(1, "libdvec: %s", \
+ (rc) == DVEC_BAD ? lut[0] : (DVEC_ERRNO(rc) ? strerror(DVEC_ERRNO(rc)) : lut[rc+1]))
+
+#define DVEC_ITER(dvec, p) (p) = NULL; ((p) = dvec_iter_fwd((dvec), (p)));
+#define DVEC_ITER_BWD(dvec, p) (p) = NULL; ((p) = dvec_iter_bwd((dvec), (p)));
+
+#define DVEC_STRERR_INIT \
+ [0] = "Failure", \
+ [1+DVEC_OK] = "Success", \
+ [1+DVEC_LOCKED] = "Locked", \
+ [1+DVEC_MODIFIED] = "Modified"
+
+#ifdef LIBDVEC_ASSERT_ARGS
+#include "stdlib.h"
+#define LIBDVEC_ABORT_ON_ARGS(cond) do { if (cond) abort(); } while (0)
+#else
+#define LIBDVEC_ABORT_ON_ARGS(cond)
+#endif
+
+#ifdef LIBDVEC_ASSERT_ALLOC
+#include "stdlib.h"
+#define LIBDVEC_ABORT_ON_ALLOC(cond) do { if (cond) abort(); } while (0)
+#else
+#define LIBDVEC_ABORT_ON_ALLOC(cond)
+#endif
+
+enum {
+ DVEC_BAD = -1 & INT_MAX,
+ DVEC_OK = 0,
+ DVEC_LOCKED = 1 << 0,
+ DVEC_MODIFIED = 1 << 1
+};
+
+struct dvec {
+ size_t dsize;
+ size_t len, cap;
+ uint8_t *data;
+ uint8_t locked : 1; /* prevents resizes */
+ uint8_t fused : 1; /* single allocation w/ data */
+ const struct allocator *allocator;
+};
+
+typedef bool (*dvec_sort_order_fn)(const void *left, const void *right, const void *user);
+typedef int (*dvec_search_cmp_fn)(const void *other, const void *user);
+
+int dvec_init(struct dvec *dvec, size_t dsize, size_t cap,
+ const struct allocator *allocator);
+int dvec_deinit(struct dvec *dvec);
+
+struct dvec *dvec_alloc(size_t dsize, size_t cap,
+ const struct allocator *allocator, int *rc);
+struct dvec *dvec_alloc_fused(size_t dsize, size_t cap,
+ const struct allocator *allocator, int *rc);
+int dvec_free(struct dvec *dvec);
+
+int dvec_copy(struct dvec *dst, struct dvec *src);
+void dvec_move(struct dvec *dst, struct dvec *src);
+void dvec_swap(struct dvec *dst, struct dvec *src);
+
+void dvec_clear(struct dvec *dvec);
+int dvec_reserve(struct dvec *dvec, size_t cap);
+int dvec_shrink(struct dvec *dvec);
+
+int dvec_add(struct dvec *dvec, size_t index, size_t count);
+void dvec_rm(struct dvec *dvec, size_t index, size_t count);
+void dvec_replace(struct dvec *dvec, size_t index,
+ const void *data, size_t count);
+
+void *dvec_iter_fwd(const struct dvec *dvec, const void *p);
+void *dvec_iter_bwd(const struct dvec *dvec, const void *p);
+
+int dvec_quick_sort(struct dvec *dvec, const struct allocator *allocator,
+ void *tmp, bool reverse, dvec_sort_order_fn in_order, const void *user);
+int dvec_quick_sort_ex(struct dvec *dvec, const struct allocator *allocator,
+ void *tmp, bool reverse, dvec_sort_order_fn in_order, const void *user);
+
+ssize_t dvec_binary_search(struct dvec *dvec, dvec_search_cmp_fn search_cmp,
+ void *user, ssize_t *lower, ssize_t *higher);
+
+static inline void *
+dvec_at(const struct dvec *dvec, size_t index)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec || index >= dvec->len);
+
+ return dvec->data + index * dvec->dsize;
+}
+
+static inline void *
+dvec_at_back(const struct dvec *dvec, size_t index)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec || !index || index >= dvec->len);
+
+ return dvec->data + (dvec->len - 1 - index) * dvec->dsize;
+}
+
+static inline void *
+dvec_front(const struct dvec *dvec)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ return dvec->data;
+}
+
+static inline void *
+dvec_back(const struct dvec *dvec)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ return dvec->data + (dvec->len - 1) * dvec->dsize;
+}
+
+static inline bool
+dvec_empty(const struct dvec *dvec)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ return !dvec->len;
+}
+
+static inline size_t
+dvec_len(const struct dvec *dvec)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ return dvec->len;
+}
+
+static inline int
+dvec_add_back(struct dvec *dvec, size_t count)
+{
+ return dvec_add(dvec, dvec->len, count);
+}
+
+static inline void
+dvec_rm_back(struct dvec *dvec, size_t count)
+{
+ dvec_rm(dvec, dvec->len - count, count);
+}
+
+static inline void
+dvec_lock(struct dvec *dvec, bool lock)
+{
+ dvec->locked = lock;
+}
+
+static inline void *
+dvec_push(struct dvec *dvec)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ dvec_reserve(dvec, dvec->len + 1);
+
+ return dvec->data + dvec->len++ * dvec->dsize;
+}
+
+static inline void *
+dvec_pop(struct dvec *dvec)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec || !dvec->len);
+
+ return dvec->data + --dvec->len * dvec->dsize;
+}
+
+static inline size_t
+dvec_idx(struct dvec *dvec, const void *p)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec || p < dvec->data
+ || p >= dvec->data + dvec->dsize * dvec->len);
+
+ return (size_t) ((uint8_t *)p - dvec->data) / dvec->dsize;
+}
diff --git a/lib/libdvec/lib/liballoc/.gitignore b/lib/libdvec/lib/liballoc/.gitignore
new file mode 100644
index 0000000..ac80ccf
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/.gitignore
@@ -0,0 +1,7 @@
+compile_commands.json
+build
+build.jst
+.cache
+vgcore*
+.gdb_history
+test
diff --git a/lib/libdvec/lib/liballoc/LICENSE b/lib/libdvec/lib/liballoc/LICENSE
new file mode 100644
index 0000000..361f116
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Louis Burda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/libdvec/lib/liballoc/Makefile b/lib/libdvec/lib/liballoc/Makefile
new file mode 100644
index 0000000..dab8818
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/Makefile
@@ -0,0 +1,45 @@
+PREFIX ?= /usr/local
+LIBDIR ?= /lib
+INCLDIR ?= /include
+
+CFLAGS = -I include
+
+ifeq "$(DEBUG)" "1"
+CFLAGS += -Og -g
+else
+CFLAGS += -O2
+endif
+
+all: build/liballoc.so build/liballoc.a build/test
+
+clean:
+ rm -rf build
+
+cleanall: clean
+
+build:
+ mkdir build
+
+build/liballoc.a: src/allocator.c | build
+ $(CC) -o build/tmp.o src/allocator.c $(CFLAGS) -r
+ objcopy --keep-global-symbols=liballoc.api build/tmp.o build/fixed.o
+ ar rcs $@ build/fixed.o
+
+build/liballoc.so: src/allocator.c include/allocator.h | build
+ $(CC) -o $@ src/allocator.c $(CFLAGS) \
+ -shared -Wl,-version-script liballoc.lds
+
+build/test: src/test.c build/liballoc.a | build
+ $(CC) -o $@ $^ -I include
+
+install:
+ install -m755 include/allocator.h -t "$(DESTDIR)$(PREFIX)$(INCLDIR)"
+ install -m755 build/liballoc.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+ install -m755 build/liballoc.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)$(INCLDIR)/allocator.h"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.a"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.so"
+
+.PHONY: all clean cleanall install uninstall
diff --git a/lib/libdvec/lib/liballoc/build.jst.tmpl b/lib/libdvec/lib/liballoc/build.jst.tmpl
new file mode 100644
index 0000000..410749d
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/build.jst.tmpl
@@ -0,0 +1,60 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include #{OPT_CFLAGS} #{EXTRA_CFLAGS}
+
+rule liba
+ gcc -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=liballoc.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ gcc -o $out $in $cflags -shared -Wl,-version-script liballoc.lds
+
+rule cc
+ gcc -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target build/liballoc.a
+ liba src/allocator.c | include/allocator.h build
+
+target build/liballoc.so
+ libso src/allocator.c | include/allocator.h build
+
+target build/test
+ cc src/test.c build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ just clean
+
+command install
+ install -m755 build/liballoc.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/liballoc.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m644 include/allocator.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.so"
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/allocator.h"
+
+command all
+ just build/liballoc.a build/liballoc.so build/test
+
diff --git a/lib/libdvec/lib/liballoc/common.mk b/lib/libdvec/lib/liballoc/common.mk
new file mode 100644
index 0000000..fe00d79
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/common.mk
@@ -0,0 +1,9 @@
+LIBALLOC_A = build/liballoc.a
+LIBALLOC_A_SRC = src/allocator.c
+LIBALLOC_A_DEP = $(LIBALLOC_A_SRC) include/allocator.h
+LIBALLOC_A_SRCDEP = $(LIBALLOC_A_DEP)
+
+LIBALLOC_SO = build/liballoc.so
+LIBALLOC_SO_SRC = src/allocator.c
+LIBALLOC_SO_DEP = $(LIBALLOC_SO_SRC) include/allocator.h
+LIBALLOC_SO_SRCDEP = $(LIBALLOC_SO_DEP)
diff --git a/lib/libdvec/lib/liballoc/configure b/lib/libdvec/lib/liballoc/configure
new file mode 100755
index 0000000..9b3f0eb
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/configure
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+tmpl "$@" build.jst.tmpl > build.jst
diff --git a/lib/libdvec/lib/liballoc/include/allocator.h b/lib/libdvec/lib/liballoc/include/allocator.h
new file mode 100644
index 0000000..fc9a13d
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/include/allocator.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <stddef.h>
+
+struct allocator {
+ void *(*alloc)(const struct allocator *allocator,
+ size_t size, int *rc);
+ void *(*realloc)(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+ int (*free)(const struct allocator *allocator, void *p);
+};
+
+struct strict_allocator {
+ const struct allocator *allocator_ul;
+ struct allocator allocator;
+};
+
+void strict_allocator_init(struct strict_allocator *strict_allocator_init,
+ const struct allocator *allocator_ul);
+
+extern const struct allocator stdlib_heap_allocator;
+extern const struct allocator stdlib_strict_heap_allocator;
diff --git a/lib/libdvec/lib/liballoc/liballoc.api b/lib/libdvec/lib/liballoc/liballoc.api
new file mode 100644
index 0000000..e875b2b
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/liballoc.api
@@ -0,0 +1,4 @@
+strict_allocator_init
+
+stdlib_heap_allocator
+stdlib_strict_heap_allocator
diff --git a/lib/libdvec/lib/liballoc/liballoc.lds b/lib/libdvec/lib/liballoc/liballoc.lds
new file mode 100644
index 0000000..07e5fca
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/liballoc.lds
@@ -0,0 +1,7 @@
+LIBALLOC_2.1 {
+ global:
+ strict_allocator_init;
+ stdlib_heap_allocator;
+ stdlib_strict_heap_allocator;
+ local: *;
+};
diff --git a/lib/libdvec/lib/liballoc/src/allocator.c b/lib/libdvec/lib/liballoc/src/allocator.c
new file mode 100644
index 0000000..eb6bc6d
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/src/allocator.c
@@ -0,0 +1,150 @@
+#include "allocator.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+static void *stdlib_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_heap_free(const struct allocator *allocator, void *p);
+
+static void *stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_strict_heap_free(const struct allocator *allocator, void *p);
+
+const struct allocator stdlib_heap_allocator = {
+ .alloc = stdlib_heap_alloc,
+ .realloc = stdlib_heap_realloc,
+ .free = stdlib_heap_free
+};
+
+const struct allocator stdlib_strict_heap_allocator = {
+ .alloc = stdlib_strict_heap_alloc,
+ .realloc = stdlib_strict_heap_realloc,
+ .free = stdlib_strict_heap_free
+};
+
+void *
+stdlib_heap_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (rc && !p) *rc = errno;
+
+ return p;
+}
+
+void *
+stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (rc && !np) *rc = errno;
+
+ return np;
+}
+
+int
+stdlib_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+stdlib_strict_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+strict_allocator_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *p;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ p = strict_allocator->allocator_ul->alloc(
+ strict_allocator->allocator_ul, size, rc);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+strict_allocator_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *np;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ np = strict_allocator->allocator_ul->realloc(
+ strict_allocator->allocator_ul, p, size, rc);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+strict_allocator_free(const struct allocator *allocator, void *p)
+{
+ const struct strict_allocator *strict_allocator;
+ int rc;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ rc = strict_allocator->allocator_ul->free(
+ strict_allocator->allocator_ul, p);
+ if (rc) abort();
+
+ return 0;
+}
+
+void
+strict_allocator_init(struct strict_allocator *strict_allocator,
+ const struct allocator *allocator_ul)
+{
+ strict_allocator->allocator_ul = allocator_ul;
+ strict_allocator->allocator.alloc = strict_allocator_alloc;
+ strict_allocator->allocator.realloc = strict_allocator_realloc;
+ strict_allocator->allocator.free = strict_allocator_free;
+}
diff --git a/lib/libdvec/lib/liballoc/src/test.c b/lib/libdvec/lib/liballoc/src/test.c
new file mode 100644
index 0000000..7b3ee44
--- /dev/null
+++ b/lib/libdvec/lib/liballoc/src/test.c
@@ -0,0 +1,22 @@
+#include "allocator.h"
+
+#include <err.h>
+#include <string.h>
+#include <stdlib.h>
+
+const struct allocator *ga = &stdlib_heap_allocator;
+
+int
+main(int argc, const char **argv)
+{
+ struct test *test;
+ int rc;
+
+ if (argc <= 1) exit(1);
+
+ test = ga->alloc(ga, strtoull(argv[1], NULL, 10), &rc);
+ if (!test) errx(1, "alloc: %s", strerror(rc));
+
+ rc = ga->free(ga, test);
+ if (rc) errx(1, "free: %s", strerror(rc));
+}
diff --git a/lib/libdvec/libdvec.api b/lib/libdvec/libdvec.api
new file mode 100644
index 0000000..a421bc3
--- /dev/null
+++ b/lib/libdvec/libdvec.api
@@ -0,0 +1,25 @@
+dvec_init
+dvec_deinit
+
+dvec_alloc
+dvec_alloc_fused
+dvec_free
+
+dvec_copy
+dvec_swap
+
+dvec_clear
+dvec_reserve
+dvec_shrink
+
+dvec_add
+dvec_rm
+dvec_replace
+
+dvec_iter_fwd
+dvec_iter_bwd
+
+dvec_quick_sort
+dvec_quick_sort_ex
+
+dvec_binary_search
diff --git a/lib/libdvec/libdvec.lds b/lib/libdvec/libdvec.lds
new file mode 100644
index 0000000..4f0fc40
--- /dev/null
+++ b/lib/libdvec/libdvec.lds
@@ -0,0 +1,29 @@
+LIBDVEC_1.1.4 {
+ global:
+ dvec_init;
+ dvec_deinit;
+
+ dvec_alloc;
+ dvec_alloc_fused;
+ dvec_free;
+
+ dvec_copy;
+ dvec_swap;
+
+ dvec_clear;
+ dvec_reserve;
+ dvec_shrink;
+
+ dvec_add;
+ dvec_rm;
+ dvec_replace;
+
+ dvec_iter_fwd;
+ dvec_iter_bwd;
+
+ dvec_quick_sort;
+ dvec_quick_sort_ex;
+
+ dvec_binary_search;
+ local: *;
+};
diff --git a/lib/libdvec/src/dvec.c b/lib/libdvec/src/dvec.c
new file mode 100644
index 0000000..bc71f3f
--- /dev/null
+++ b/lib/libdvec/src/dvec.c
@@ -0,0 +1,510 @@
+#include "dvec.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#define ERR(x) ((x) >= 0 ? DVEC_BAD : (x))
+
+struct sort_order_user_proxy {
+ dvec_sort_order_fn order_fn;
+ const void *user;
+};
+
+int
+dvec_init(struct dvec *dvec, size_t dsize, size_t cap,
+ const struct allocator *allocator)
+{
+ int rc = 0;
+
+ LIBDVEC_ABORT_ON_ARGS(!dvec || !dsize || !allocator);
+
+ dvec->allocator = allocator;
+ dvec->dsize = dsize;
+ dvec->cap = cap;
+ dvec->len = 0;
+ dvec->data = NULL;
+ dvec->locked = false;
+ dvec->fused = false;
+ if (dvec->cap) {
+ dvec->data = allocator->alloc(allocator, cap * dsize, &rc);
+ LIBDVEC_ABORT_ON_ALLOC(!dvec->data);
+ if (!dvec->data) return ERR(rc);
+ }
+
+ return DVEC_OK;
+}
+
+int
+dvec_deinit(struct dvec *dvec)
+{
+ int rc;
+
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ rc = dvec->allocator->free(dvec->allocator, dvec->data);
+ LIBDVEC_ABORT_ON_ALLOC(rc);
+
+ return rc >= 0 ? DVEC_OK : ERR(rc);
+}
+
+struct dvec *
+dvec_alloc(size_t dsize, size_t cap,
+ const struct allocator *allocator, int *rc)
+{
+ struct dvec *dvec;
+ int _rc;
+
+ LIBDVEC_ABORT_ON_ARGS(!allocator);
+
+ dvec = allocator->alloc(allocator, sizeof(struct dvec), &_rc);
+ LIBDVEC_ABORT_ON_ALLOC(!dvec);
+ if (!dvec) {
+ if (rc) *rc = ERR(_rc);
+ return NULL;
+ }
+
+ _rc = dvec_init(dvec, dsize, cap, allocator);
+ if (_rc < 0) {
+ if (rc) *rc = _rc;
+ allocator->free(allocator, dvec);
+ return NULL;
+ }
+
+ return dvec;
+}
+
+static size_t
+aligned_size(size_t size, size_t dsize)
+{
+ return size + (size % dsize ? dsize - size % dsize : 0);
+}
+
+struct dvec *
+dvec_alloc_fused(size_t dsize, size_t cap,
+ const struct allocator *allocator, int *rc)
+{
+ struct dvec *dvec;
+ size_t off;
+ int _rc = 0;
+
+ LIBDVEC_ABORT_ON_ARGS(!allocator);
+
+ off = aligned_size(sizeof(struct dvec), dsize);
+ dvec = allocator->alloc(allocator, off + cap * dsize, &_rc);
+ if (!dvec) {
+ if (_rc) *rc = ERR(_rc);
+ return NULL;
+ }
+
+ dvec->allocator = allocator;
+ dvec->dsize = dsize;
+ dvec->cap = cap;
+ dvec->len = 0;
+ dvec->data = (uint8_t *) dvec + off;
+ dvec->locked = false;
+ dvec->fused = true;
+
+ return dvec;
+}
+
+int
+dvec_free(struct dvec *dvec)
+{
+ const struct allocator *allocator;
+ int rc;
+
+ if (!dvec) return DVEC_OK;
+
+ allocator = dvec->allocator;
+
+ if (dvec->fused) {
+ rc = allocator->free(allocator, dvec);
+ if (rc < 0) return rc;
+ } else {
+ rc = dvec_deinit(dvec);
+ if (rc) return rc;
+
+ rc = allocator->free(allocator, dvec);
+ LIBDVEC_ABORT_ON_ALLOC(rc);
+ if (rc < 0) return rc;
+ }
+
+ return DVEC_OK;
+}
+
+int
+dvec_copy(struct dvec *dst, struct dvec *src)
+{
+ int rc;
+
+ LIBDVEC_ABORT_ON_ARGS(!dst || !src || dst->dsize != src->dsize);
+
+ rc = dvec_reserve(dst, src->len);
+ if (rc) return rc;
+
+ dst->len = src->len;
+ memcpy(dst->data, src->data, src->len * src->dsize);
+
+ return DVEC_OK;
+}
+
+void
+dvec_move(struct dvec *dst, struct dvec *src)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dst || !src);
+
+ memcpy(dst, src, sizeof(struct dvec));
+}
+
+void
+dvec_swap(struct dvec *dst, struct dvec *src)
+{
+ struct dvec tmp;
+
+ LIBDVEC_ABORT_ON_ARGS(!dst || !src);
+
+ memcpy(&tmp, dst, sizeof(struct dvec));
+ memcpy(dst, src, sizeof(struct dvec));
+ memcpy(src, &tmp, sizeof(struct dvec));
+}
+
+void
+dvec_clear(struct dvec *dvec)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ dvec->len = 0;
+}
+
+int
+dvec_reserve(struct dvec *dvec, size_t len)
+{
+ void *data;
+ size_t cap, off;
+ int rc = 1;
+
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ if (len <= dvec->cap) return DVEC_OK;
+
+ if (dvec->locked) return DVEC_LOCKED;
+
+ cap = 2 * dvec->cap;
+ if (len > cap) cap = len;
+
+ if (dvec->fused) {
+ off = aligned_size(sizeof(struct dvec), dvec->dsize);
+ data = dvec->allocator->realloc(dvec->allocator, dvec,
+ off + dvec->cap * dvec->dsize, &rc);
+ LIBDVEC_ABORT_ON_ALLOC(!data);
+ if (!data) return ERR(rc);
+ dvec = data;
+ dvec->data = (uint8_t *) dvec + off;
+ } else {
+ data = dvec->allocator->realloc(dvec->allocator,
+ dvec->data, cap * dvec->dsize, &rc);
+ LIBDVEC_ABORT_ON_ALLOC(!data);
+ if (!data) return ERR(rc);
+ dvec->data = data;
+ }
+
+ dvec->cap = cap;
+
+ return DVEC_OK;
+}
+
+int
+dvec_shrink(struct dvec *dvec)
+{
+ void *data;
+ size_t cap, off;
+ int rc = 0;
+
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+
+ if (dvec->locked) return DVEC_LOCKED;
+
+ cap = dvec->len;
+
+ if (dvec->fused) {
+ off = aligned_size(sizeof(struct dvec), dvec->dsize);
+ data = dvec->allocator->realloc(dvec->allocator, dvec,
+ off + cap * dvec->dsize, &rc);
+ LIBDVEC_ABORT_ON_ALLOC(!data);
+ if (!data) return ERR(rc);
+ dvec = data;
+ dvec->data = (uint8_t *) dvec + off;
+ } else if (!dvec->len) {
+ rc = dvec->allocator->free(dvec->allocator, dvec->data);
+ LIBDVEC_ABORT_ON_ALLOC(rc);
+ if (rc < 0) return rc;
+ } else {
+ data = dvec->allocator->realloc(dvec->allocator,
+ &dvec->data, cap * dvec->dsize, &rc);
+ LIBDVEC_ABORT_ON_ALLOC(!data);
+ if (!data) return ERR(rc);
+ dvec->data = data;
+ }
+
+ dvec->cap = cap;
+
+ return DVEC_OK;
+}
+
+int
+dvec_add(struct dvec *dvec, size_t index, size_t len)
+{
+ int rc;
+
+ LIBDVEC_ABORT_ON_ARGS(!dvec || index > dvec->len);
+
+ rc = dvec_reserve(dvec, dvec->len + len);
+ if (rc) return rc;
+
+ if (index < dvec->len) {
+ memmove(dvec->data + (index + len) * dvec->dsize,
+ dvec->data + index * dvec->dsize,
+ (dvec->len - index) * dvec->dsize);
+ }
+
+ dvec->len += len;
+
+ return DVEC_OK;
+}
+
+void
+dvec_rm(struct dvec *dvec, size_t index, size_t count)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec || index + count > dvec->len);
+
+ if (index + count < dvec->len) {
+ memmove(dvec->data + index * dvec->dsize,
+ dvec->data + (index + count) * dvec->dsize,
+ (dvec->len - (index + count)) * dvec->dsize);
+ }
+
+ dvec->len -= count;
+}
+
+void
+dvec_replace(struct dvec *dvec, size_t index, const void *data, size_t count)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec || !data || index + count > dvec->len);
+
+ memcpy(dvec->data + index * dvec->dsize, data, count * dvec->dsize);
+}
+
+void *
+dvec_iter_fwd(const struct dvec *dvec, const void *p)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+ LIBDVEC_ABORT_ON_ARGS(p && (p < dvec->data));
+ LIBDVEC_ABORT_ON_ARGS(p && (p >= dvec->data + dvec->len * dvec->dsize));
+ LIBDVEC_ABORT_ON_ARGS(p && ((size_t) (p - dvec->data) % dvec->dsize));
+
+ if (p == NULL) {
+ if (dvec->len > 0)
+ return dvec->data;
+ else
+ return NULL;
+ } else if ((uint8_t *) p < dvec->data + dvec->dsize * (dvec->len - 1)) {
+ return (uint8_t *) p + dvec->dsize;
+ } else {
+ return NULL;
+ }
+}
+
+void *
+dvec_iter_bwd(const struct dvec *dvec, const void *p)
+{
+ LIBDVEC_ABORT_ON_ARGS(!dvec);
+ LIBDVEC_ABORT_ON_ARGS(p && (p < dvec->data));
+ LIBDVEC_ABORT_ON_ARGS(p && (p >= dvec->data + dvec->len * dvec->dsize));
+ LIBDVEC_ABORT_ON_ARGS(p && ((size_t) (p - dvec->data) % dvec->dsize));
+
+ if (p == NULL) {
+ if (dvec->len > 0)
+ return dvec->data + (dvec->len - 1) * dvec->dsize;
+ else
+ return NULL;
+ } else if ((uint8_t *) p > dvec->data) {
+ return (uint8_t *) p - dvec->dsize;
+ } else {
+ return NULL;
+ }
+}
+
+static void
+dvec_quick_sort_swap(void *a, void *b, void *tmp, size_t dsize)
+{
+ memcpy(tmp, a, dsize);
+ memcpy(a, b, dsize);
+ memcpy(b, tmp, dsize);
+}
+
+int
+dvec_quick_sort(struct dvec *dvec, const struct allocator *allocator,
+ void *tmp, bool reverse, dvec_sort_order_fn in_order, const void *user)
+{
+ const size_t dsize = dvec->dsize;
+ struct dvec stack;
+ uint8_t *start, *end;
+ uint8_t *lo, *hi, *pivot;
+ bool lo_order, hi_order;
+ bool modified = false;
+ int rc;
+
+ if (!dvec->len) return DVEC_OK;
+
+ rc = dvec_init(&stack, sizeof(uint8_t *), 32, allocator);
+ if (rc) return rc;
+
+ *(uint8_t **)dvec_push(&stack) = dvec_front(dvec);
+ *(uint8_t **)dvec_push(&stack) = dvec_back(dvec);
+
+ while (!dvec_empty(&stack)) {
+ end = *(uint8_t **)dvec_pop(&stack);
+ start = *(uint8_t **)dvec_pop(&stack);
+
+ if (start == end) continue;
+
+ if (start + dsize >= end) {
+ if (!in_order(start, end, user)) {
+ dvec_quick_sort_swap(start, end, tmp, dsize);
+ modified = true;
+ }
+ continue;
+ }
+
+ lo = start;
+ hi = end;
+ pivot = start + (size_t) (end - start) / dsize / 2 * dsize;
+
+ while (lo < hi) {
+ lo_order = lo != pivot && in_order(lo, pivot, user);
+ hi_order = hi != pivot && in_order(pivot, hi, user);
+ if (!lo_order && !hi_order) {
+ if (lo == pivot) pivot = hi;
+ else if (hi == pivot) pivot = lo;
+ dvec_quick_sort_swap(lo, hi, tmp, dsize);
+ modified = true;
+ }
+ if (lo_order) lo += dsize;
+ if (hi_order) hi -= dsize;
+ }
+
+ if (start != pivot) {
+ *(uint8_t **)dvec_push(&stack) = start;
+ *(uint8_t **)dvec_push(&stack) = pivot - dsize;
+ }
+
+ if (end != pivot) {
+ *(uint8_t **)dvec_push(&stack) = pivot + dsize;
+ *(uint8_t **)dvec_push(&stack) = end;
+ }
+ }
+
+ dvec_deinit(&stack);
+
+ return modified ? DVEC_MODIFIED : DVEC_OK;
+}
+
+static bool
+dvec_quick_sort_ex_order(const void *p1, const void *p2, const void *user)
+{
+ const struct sort_order_user_proxy *user_proxy = user;
+ const void *d1 = *(void **)p1, *d2 = *(void **)p2;
+
+ return user_proxy->order_fn(d1, d2, user_proxy->user);
+}
+
+int
+dvec_quick_sort_ex(struct dvec *dvec, const struct allocator *allocator,
+ void *tmp, bool reverse, dvec_sort_order_fn in_order, const void *user)
+{
+ const struct sort_order_user_proxy user_proxy = {
+ .order_fn = in_order,
+ .user = user
+ };
+ struct dvec ptrmap;
+ void *p, **pp, **dst, **src;
+ size_t dsize;
+ int rc;
+
+ if (!dvec->len) return DVEC_OK;
+
+ dsize = dvec->dsize;
+
+ rc = dvec_init(&ptrmap, sizeof(void *), dvec->len, allocator);
+ if (rc) return rc;
+
+ for (DVEC_ITER(dvec, p))
+ *(void **)dvec_push(&ptrmap) = p;
+
+ rc = dvec_quick_sort(&ptrmap, allocator, tmp, reverse,
+ dvec_quick_sort_ex_order, &user_proxy);
+ if (rc) return rc;
+
+ for (DVEC_ITER(&ptrmap, pp)) {
+ if (!*pp || dvec_idx(&ptrmap, pp) == dvec_idx(dvec, *pp))
+ continue;
+ dst = pp;
+ memcpy(tmp, *dst, dsize);
+ while (1) {
+ src = dvec_at(&ptrmap, dvec_idx(dvec, *dst));
+ if (!*src) break;
+ memcpy(*dst, *src, dsize);
+ *dst = NULL;
+ dst = src;
+ }
+ memcpy(*dst, tmp, dsize);
+ }
+
+ rc = dvec_deinit(&ptrmap);
+ if (rc) return rc;
+
+ return DVEC_OK;
+}
+
+ssize_t
+dvec_binary_search(struct dvec *dvec, dvec_search_cmp_fn search_cmp,
+ void *user, ssize_t *lower, ssize_t *higher)
+{
+ size_t min, max, pos;
+ int rc;
+
+ min = 0;
+ max = dvec->len;
+ if (lower) *lower = -1;
+ if (higher) *higher = -1;
+
+ while (min < max) {
+ pos = min + (max - min) / 2;
+ rc = search_cmp(dvec_at(dvec, pos), user);
+ if (!rc) {
+ if (lower && pos > 0)
+ *lower = (ssize_t) pos - 1;
+ if (higher && pos < dvec->len - 1)
+ *higher = (ssize_t) pos + 1;
+ return (ssize_t) pos;
+ } else if (rc > 0) {
+ if (pos + 1 == max) {
+ if (lower) *lower = (ssize_t) min;
+ if (higher && min < dvec->len - 1)
+ *higher = (ssize_t) min + 1;
+ break;
+ }
+ min = pos + 1;
+ } else {
+ if (min == pos) {
+ if (higher) *higher = (ssize_t) min;
+ if (lower && min > 0)
+ *lower = (ssize_t) min - 1;
+ break;
+ }
+ max = pos;
+ }
+ }
+
+ return -1;
+}
diff --git a/lib/libdvec/src/test.c b/lib/libdvec/src/test.c
new file mode 100644
index 0000000..af0ee24
--- /dev/null
+++ b/lib/libdvec/src/test.c
@@ -0,0 +1,77 @@
+#include "allocator.h"
+#include "dvec.h"
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+static const char *dvec_err[] = {
+ DVEC_STRERR_INIT
+};
+
+bool
+str_sort_order(const void *p1, const void *p2, const void *user)
+{
+ const char *s1 = *(const char **)p1;
+ const char *s2 = *(const char **)p2;
+
+ return strcmp(s1, s2) <= 0;
+}
+
+int
+str_search(const void *p, const void *user)
+{
+ const char *str = *(const char **)p, *cmp = user;
+
+ return strcmp(cmp, str);
+}
+
+int
+main(int argc, const char **argv)
+{
+ struct dvec dvec, *fused;
+ ssize_t below, above, on;
+ const char **val, *tmp;
+ int i, rc;
+
+ rc = dvec_init(&dvec, sizeof(const char *), 16, &stdlib_heap_allocator);
+ if (rc) DVEC_ERR(dvec_err, rc);
+
+ for (i = 1; i < argc; i++) {
+ rc = dvec_add_back(&dvec, 1);
+ if (rc) DVEC_ERR(dvec_err, rc);
+ val = dvec_back(&dvec);
+ *val = argv[i];
+ }
+
+ rc = dvec_quick_sort_ex(&dvec, &stdlib_heap_allocator, &tmp,
+ false, str_sort_order, NULL);
+ if (rc < 0) DVEC_ERR(dvec_err, rc);
+
+ on = dvec_binary_search(&dvec, str_search, "abc", &below, &above);
+
+ for (DVEC_ITER(&dvec, val))
+ printf("%s\n", *val);
+
+ printf("dvec len: %lu\n", dvec.len);
+
+ printf("\n");
+ printf("below: %li\n", below);
+ printf("on: %li\n", on);
+ printf("above: %li\n", above);
+ printf("\n");
+
+ fused = dvec_alloc_fused(sizeof(const char *), 0,
+ &stdlib_heap_allocator, &rc);
+ if (!fused) DVEC_ERR(dvec_err, rc);
+
+ printf("fused: %p %p (+%li)\n", (void *) fused,
+ fused->data, fused->data - (uint8_t *) fused);
+
+ rc = dvec_free(fused);
+ if (rc) DVEC_ERR(dvec_err, rc);
+
+ dvec_deinit(&dvec);
+}
diff --git a/lib/libhmap b/lib/libhmap
deleted file mode 160000
-Subproject 274addaba036c4dafda702f4dd449ab3157565d
diff --git a/lib/libhmap/.gitignore b/lib/libhmap/.gitignore
new file mode 100644
index 0000000..a64dd56
--- /dev/null
+++ b/lib/libhmap/.gitignore
@@ -0,0 +1,7 @@
+compile_commands.json
+build
+build.rmk
+.cache
+vgcore*
+test
+/.subgit
diff --git a/lib/libhmap/.subgitrc b/lib/libhmap/.subgitrc
new file mode 100644
index 0000000..5f1b14f
--- /dev/null
+++ b/lib/libhmap/.subgitrc
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+declare -A subgit subgitinfo
+
+subgit[lib/liballoc]=1
+subgitinfo[lib/liballoc/remote]='git@sinitax.com:sinitax/liballoc-c'
+subgitinfo[lib/liballoc/branch]='master'
+subgitinfo[lib/liballoc/commit]='24d37e4298575df36399f32a6fa5ef10b005c964'
+
diff --git a/lib/libhmap/LICENSE b/lib/libhmap/LICENSE
new file mode 100644
index 0000000..361f116
--- /dev/null
+++ b/lib/libhmap/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Louis Burda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/libhmap/Makefile b/lib/libhmap/Makefile
new file mode 100644
index 0000000..3adad36
--- /dev/null
+++ b/lib/libhmap/Makefile
@@ -0,0 +1,59 @@
+PREFIX ?= /usr/local
+INCLDIR ?= /include
+LIBDIR ?= /lib
+
+CFLAGS += -I include -I lib/liballoc/include
+CFLAGS += -Wunused-function -Wunused-variable -Wno-prototype
+CFLAGS += -Wconversion -Wsign-compare -Werror
+
+ifeq "$(DEBUG)" "1"
+CFLAGS += -Og -g
+else
+CFLAGS += -O2
+endif
+
+ifeq "$(ASSERT_ARGS)" "1"
+CFLAGS += -DLIBHMAP_ASSERT_ARGS=1
+endif
+
+ifeq "$(ASSERT_ALLOC)" "1"
+CFLAGS += -DLIBHMAP_ASSERT_ALLLOC=1
+endif
+
+all: build/libhmap.so build/libhmap.a build/test
+
+clean:
+ rm -rf build
+
+cleanall: clean
+ make -C lib/liballoc cleanall
+
+build:
+ mkdir build
+
+lib/liballoc/build/liballoc.a:
+ make -C lib/liballoc build/liballoc.a
+
+build/libhmap.a: src/hmap.c include/hmap.h libhmap.api | build
+ $(CC) -o build/tmp.o src/hmap.c $(CFLAGS) -r
+ objcopy --keep-global-symbols=libhmap.api build/tmp.o build/fixed.o
+ $(AR) rcs $@ build/fixed.o
+
+build/libhmap.so: src/hmap.c include/hmap.h libhmap.lds | build
+ $(CC) -o $@ src/hmap.c -fPIC $(CFLAGS) -shared \
+ -Wl,-version-script libhmap.lds
+
+build/test: src/test.c build/libhmap.a lib/liballoc/build/liballoc.a | build
+ $(CC) -o $@ $^ -I include -I lib/liballoc/include -g
+
+install:
+ install -m 644 include/hmap.h -t "$(DESTDIR)$(PREFIX)$(INCLDIR)"
+ install -m 755 build/libhmap.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+ install -m 755 build/libhmap.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)$(INCLDIR)/hmap.h"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/libhmap.a"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/libhmap.so"
+
+.PHONY: all clean cleanall install uninstall
diff --git a/lib/libhmap/build.rmk.tmpl b/lib/libhmap/build.rmk.tmpl
new file mode 100644
index 0000000..b123dcf
--- /dev/null
+++ b/lib/libhmap/build.rmk.tmpl
@@ -0,0 +1,69 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef LIBHMAP_DEBUG
+#define DEBUG 1
+#endif
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include -I lib/liballoc/include #{OPT_CFLAGS}
+ #{EXTRA_CFLAGS} #{LIBHMAP_EXTRA_CFLAGS}
+
+rule mkdir
+ mkdir $out
+
+rule liba
+ #{CC} -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=libhmap.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ #{CC} -o $out $in $cflags -shared -Wl,-version-script libhmap.lds
+
+rule cc
+ #{CC} -o $out $in $cflags
+
+target build
+ mkdir
+
+target lib/liballoc/build/liballoc.a
+ rmk lib/liballoc
+
+target build/libhmap.a
+ liba src/hmap.c | include/hmap.h build
+
+target build/libhmap.so
+ libso src/hmap.c | include/hmap.h build
+
+target build/test
+ cc src/test.c build/libhmap.a lib/liballoc/build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ rmk clean
+ rmk -C lib/liballoc cleanall
+
+command install
+ install -m755 build/libhmap.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/libhmap.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m644 include/hmap.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libhmap.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libhmap.so"
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/hmap.h"
+
+command all
+ rmk build/libhmap.a build/libhmap.so build/test
+
diff --git a/lib/libhmap/configure b/lib/libhmap/configure
new file mode 100755
index 0000000..b4dd05d
--- /dev/null
+++ b/lib/libhmap/configure
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+tmpl "$@" build.rmk.tmpl > build.rmk
+for lib in ./lib/*; do
+ pushd $lib
+ ./configure "$@"
+ popd
+done
diff --git a/lib/libhmap/include/hmap.h b/lib/libhmap/include/hmap.h
new file mode 100644
index 0000000..3700c6e
--- /dev/null
+++ b/lib/libhmap/include/hmap.h
@@ -0,0 +1,123 @@
+#pragma once
+
+#include "allocator.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#define HMAP_ITER(map, iter) \
+ hmap_iter_init(&(iter)); hmap_iter_next(map, &(iter));
+
+#define HMAP_STRERR_INIT \
+ [HMAP_OK] = "Success", \
+ [HMAP_KEY_EXISTS] = "Key exists", \
+ [HMAP_KEY_MISSING] = "Key missing"
+
+#ifdef LIBHMAP_ASSERT_ARGS
+#define LIBHMAP_ABORT_ON_ARGS(cond) do { if (cond) abort(); } while (0)
+#else
+#define LIBHMAP_ABORT_ON_ARGS(cond)
+#endif
+
+#ifdef LIBHMAP_ASSERT_ALLOC
+#define LIBHMAP_ABORT_ON_ALLOC(cond) do { if (cond) abort(); } while (0)
+#else
+#define LIBHMAP_ABORT_ON_ALLOC(cond)
+#endif
+
+struct hmap_key;
+
+typedef uint32_t (*hmap_hash_func)(struct hmap_key key);
+typedef bool (*hmap_keycmp_func)(struct hmap_key k1, struct hmap_key k2);
+
+enum {
+ HMAP_OK = 0,
+ HMAP_KEY_EXISTS,
+ HMAP_KEY_MISSING,
+};
+
+struct hmap_key {
+ union {
+ bool b;
+ char c;
+ float f;
+ double d;
+ int i;
+ unsigned int u;
+ size_t s;
+ ssize_t ss;
+ const void *p;
+ void *_p;
+ };
+};
+
+struct hmap_val {
+ union {
+ bool b;
+ char c;
+ float f;
+ double d;
+ int i;
+ unsigned int u;
+ size_t s;
+ ssize_t ss;
+ const void *p;
+ void *_p;
+ };
+};
+
+struct hmap_link {
+ struct hmap_key key;
+ struct hmap_val value;
+ struct hmap_link *next;
+};
+
+struct hmap_iter {
+ struct hmap_link *link;
+ size_t bucket;
+};
+
+struct hmap {
+ hmap_hash_func hash;
+ hmap_keycmp_func keycmp;
+ struct hmap_link **buckets;
+ size_t bucketcnt;
+ const struct allocator *allocator;
+};
+
+int hmap_init(struct hmap *map, size_t buckets, hmap_hash_func hasher,
+ hmap_keycmp_func keycmp, const struct allocator *allocator);
+int hmap_deinit(struct hmap *map);
+
+struct hmap *hmap_alloc(size_t buckets, hmap_hash_func hasher,
+ hmap_keycmp_func keycmp, const struct allocator *allocator, int *rc);
+int hmap_free(struct hmap *map);
+
+int hmap_copy(const struct hmap *dst, const struct hmap *src);
+void hmap_swap(struct hmap *m1, struct hmap *m2);
+void hmap_clear(const struct hmap *map);
+
+struct hmap_link **hmap_link_get(const struct hmap *map, struct hmap_key key);
+struct hmap_link **hmap_link_pos(const struct hmap *map, struct hmap_key key);
+struct hmap_link *hmap_link_pop(const struct hmap *map, struct hmap_link **linkp);
+struct hmap_link *hmap_link_alloc(const struct hmap *map,
+ struct hmap_key key, struct hmap_val value, int *rc);
+
+struct hmap_link *hmap_get(const struct hmap *map, struct hmap_key key);
+int hmap_set(const struct hmap *map, struct hmap_key key, struct hmap_val value);
+int hmap_rm(const struct hmap *map, struct hmap_key key);
+int hmap_add(const struct hmap *map, struct hmap_key key, struct hmap_val value);
+
+void hmap_iter_init(struct hmap_iter *iter);
+bool hmap_iter_next(const struct hmap *map, struct hmap_iter *iter);
+static inline bool hmap_iter_done(const struct hmap_iter *iter);
+
+uint32_t hmap_str_hash(struct hmap_key key);
+bool hmap_str_keycmp(struct hmap_key k1, struct hmap_key k2);
+
+static inline bool
+hmap_iter_done(const struct hmap_iter *iter)
+{
+ return !iter->link;
+}
diff --git a/lib/libhmap/lib/liballoc/.gitignore b/lib/libhmap/lib/liballoc/.gitignore
new file mode 100644
index 0000000..ac80ccf
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/.gitignore
@@ -0,0 +1,7 @@
+compile_commands.json
+build
+build.jst
+.cache
+vgcore*
+.gdb_history
+test
diff --git a/lib/libhmap/lib/liballoc/LICENSE b/lib/libhmap/lib/liballoc/LICENSE
new file mode 100644
index 0000000..361f116
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Louis Burda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/libhmap/lib/liballoc/Makefile b/lib/libhmap/lib/liballoc/Makefile
new file mode 100644
index 0000000..dab8818
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/Makefile
@@ -0,0 +1,45 @@
+PREFIX ?= /usr/local
+LIBDIR ?= /lib
+INCLDIR ?= /include
+
+CFLAGS = -I include
+
+ifeq "$(DEBUG)" "1"
+CFLAGS += -Og -g
+else
+CFLAGS += -O2
+endif
+
+all: build/liballoc.so build/liballoc.a build/test
+
+clean:
+ rm -rf build
+
+cleanall: clean
+
+build:
+ mkdir build
+
+build/liballoc.a: src/allocator.c | build
+ $(CC) -o build/tmp.o src/allocator.c $(CFLAGS) -r
+ objcopy --keep-global-symbols=liballoc.api build/tmp.o build/fixed.o
+ ar rcs $@ build/fixed.o
+
+build/liballoc.so: src/allocator.c include/allocator.h | build
+ $(CC) -o $@ src/allocator.c $(CFLAGS) \
+ -shared -Wl,-version-script liballoc.lds
+
+build/test: src/test.c build/liballoc.a | build
+ $(CC) -o $@ $^ -I include
+
+install:
+ install -m755 include/allocator.h -t "$(DESTDIR)$(PREFIX)$(INCLDIR)"
+ install -m755 build/liballoc.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+ install -m755 build/liballoc.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)$(INCLDIR)/allocator.h"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.a"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.so"
+
+.PHONY: all clean cleanall install uninstall
diff --git a/lib/libhmap/lib/liballoc/build.jst.tmpl b/lib/libhmap/lib/liballoc/build.jst.tmpl
new file mode 100644
index 0000000..410749d
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/build.jst.tmpl
@@ -0,0 +1,60 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include #{OPT_CFLAGS} #{EXTRA_CFLAGS}
+
+rule liba
+ gcc -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=liballoc.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ gcc -o $out $in $cflags -shared -Wl,-version-script liballoc.lds
+
+rule cc
+ gcc -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target build/liballoc.a
+ liba src/allocator.c | include/allocator.h build
+
+target build/liballoc.so
+ libso src/allocator.c | include/allocator.h build
+
+target build/test
+ cc src/test.c build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ just clean
+
+command install
+ install -m755 build/liballoc.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/liballoc.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m644 include/allocator.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.so"
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/allocator.h"
+
+command all
+ just build/liballoc.a build/liballoc.so build/test
+
diff --git a/lib/libhmap/lib/liballoc/common.mk b/lib/libhmap/lib/liballoc/common.mk
new file mode 100644
index 0000000..fe00d79
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/common.mk
@@ -0,0 +1,9 @@
+LIBALLOC_A = build/liballoc.a
+LIBALLOC_A_SRC = src/allocator.c
+LIBALLOC_A_DEP = $(LIBALLOC_A_SRC) include/allocator.h
+LIBALLOC_A_SRCDEP = $(LIBALLOC_A_DEP)
+
+LIBALLOC_SO = build/liballoc.so
+LIBALLOC_SO_SRC = src/allocator.c
+LIBALLOC_SO_DEP = $(LIBALLOC_SO_SRC) include/allocator.h
+LIBALLOC_SO_SRCDEP = $(LIBALLOC_SO_DEP)
diff --git a/lib/libhmap/lib/liballoc/configure b/lib/libhmap/lib/liballoc/configure
new file mode 100755
index 0000000..9b3f0eb
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/configure
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+tmpl "$@" build.jst.tmpl > build.jst
diff --git a/lib/libhmap/lib/liballoc/include/allocator.h b/lib/libhmap/lib/liballoc/include/allocator.h
new file mode 100644
index 0000000..fc9a13d
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/include/allocator.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <stddef.h>
+
+struct allocator {
+ void *(*alloc)(const struct allocator *allocator,
+ size_t size, int *rc);
+ void *(*realloc)(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+ int (*free)(const struct allocator *allocator, void *p);
+};
+
+struct strict_allocator {
+ const struct allocator *allocator_ul;
+ struct allocator allocator;
+};
+
+void strict_allocator_init(struct strict_allocator *strict_allocator_init,
+ const struct allocator *allocator_ul);
+
+extern const struct allocator stdlib_heap_allocator;
+extern const struct allocator stdlib_strict_heap_allocator;
diff --git a/lib/libhmap/lib/liballoc/liballoc.api b/lib/libhmap/lib/liballoc/liballoc.api
new file mode 100644
index 0000000..e875b2b
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/liballoc.api
@@ -0,0 +1,4 @@
+strict_allocator_init
+
+stdlib_heap_allocator
+stdlib_strict_heap_allocator
diff --git a/lib/libhmap/lib/liballoc/liballoc.lds b/lib/libhmap/lib/liballoc/liballoc.lds
new file mode 100644
index 0000000..07e5fca
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/liballoc.lds
@@ -0,0 +1,7 @@
+LIBALLOC_2.1 {
+ global:
+ strict_allocator_init;
+ stdlib_heap_allocator;
+ stdlib_strict_heap_allocator;
+ local: *;
+};
diff --git a/lib/libhmap/lib/liballoc/src/allocator.c b/lib/libhmap/lib/liballoc/src/allocator.c
new file mode 100644
index 0000000..eb6bc6d
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/src/allocator.c
@@ -0,0 +1,150 @@
+#include "allocator.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+static void *stdlib_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_heap_free(const struct allocator *allocator, void *p);
+
+static void *stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_strict_heap_free(const struct allocator *allocator, void *p);
+
+const struct allocator stdlib_heap_allocator = {
+ .alloc = stdlib_heap_alloc,
+ .realloc = stdlib_heap_realloc,
+ .free = stdlib_heap_free
+};
+
+const struct allocator stdlib_strict_heap_allocator = {
+ .alloc = stdlib_strict_heap_alloc,
+ .realloc = stdlib_strict_heap_realloc,
+ .free = stdlib_strict_heap_free
+};
+
+void *
+stdlib_heap_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (rc && !p) *rc = errno;
+
+ return p;
+}
+
+void *
+stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (rc && !np) *rc = errno;
+
+ return np;
+}
+
+int
+stdlib_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+stdlib_strict_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+strict_allocator_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *p;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ p = strict_allocator->allocator_ul->alloc(
+ strict_allocator->allocator_ul, size, rc);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+strict_allocator_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *np;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ np = strict_allocator->allocator_ul->realloc(
+ strict_allocator->allocator_ul, p, size, rc);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+strict_allocator_free(const struct allocator *allocator, void *p)
+{
+ const struct strict_allocator *strict_allocator;
+ int rc;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ rc = strict_allocator->allocator_ul->free(
+ strict_allocator->allocator_ul, p);
+ if (rc) abort();
+
+ return 0;
+}
+
+void
+strict_allocator_init(struct strict_allocator *strict_allocator,
+ const struct allocator *allocator_ul)
+{
+ strict_allocator->allocator_ul = allocator_ul;
+ strict_allocator->allocator.alloc = strict_allocator_alloc;
+ strict_allocator->allocator.realloc = strict_allocator_realloc;
+ strict_allocator->allocator.free = strict_allocator_free;
+}
diff --git a/lib/libhmap/lib/liballoc/src/test.c b/lib/libhmap/lib/liballoc/src/test.c
new file mode 100644
index 0000000..7b3ee44
--- /dev/null
+++ b/lib/libhmap/lib/liballoc/src/test.c
@@ -0,0 +1,22 @@
+#include "allocator.h"
+
+#include <err.h>
+#include <string.h>
+#include <stdlib.h>
+
+const struct allocator *ga = &stdlib_heap_allocator;
+
+int
+main(int argc, const char **argv)
+{
+ struct test *test;
+ int rc;
+
+ if (argc <= 1) exit(1);
+
+ test = ga->alloc(ga, strtoull(argv[1], NULL, 10), &rc);
+ if (!test) errx(1, "alloc: %s", strerror(rc));
+
+ rc = ga->free(ga, test);
+ if (rc) errx(1, "free: %s", strerror(rc));
+}
diff --git a/lib/libhmap/libhmap.api b/lib/libhmap/libhmap.api
new file mode 100644
index 0000000..768193b
--- /dev/null
+++ b/lib/libhmap/libhmap.api
@@ -0,0 +1,25 @@
+hmap_init
+hmap_deinit
+hmap_alloc
+hmap_free
+
+hmap_copy
+hmap_swap
+hmap_clear
+
+hmap_link_get
+hmap_link_pos
+hmap_link_pop
+hmap_link_set
+hmap_link_alloc
+
+hmap_get
+hmap_rm
+hmap_set
+hmap_add
+
+hmap_iter_init
+hmap_iter_next
+
+hmap_str_hash
+hmap_str_keycmp
diff --git a/lib/libhmap/libhmap.lds b/lib/libhmap/libhmap.lds
new file mode 100644
index 0000000..e3ac91f
--- /dev/null
+++ b/lib/libhmap/libhmap.lds
@@ -0,0 +1,27 @@
+LIBHMAP_1.0.0 {
+ hmap_init;
+ hmap_deinit;
+ hmap_alloc;
+ hmap_free;
+
+ hmap_copy;
+ hmap_swap;
+ hmap_clear;
+
+ hmap_link_get;
+ hmap_link_pos;
+ hmap_link_pop;
+ hmap_link_set;
+ hmap_link_add;
+ hmap_link_alloc;
+
+ hmap_get;
+ hmap_rm;
+ hmap_set;
+
+ hmap_iter_init;
+ hmap_iter_next;
+
+ hmap_str_hash;
+ hmap_str_keycmp;
+};
diff --git a/lib/libhmap/src/hmap.c b/lib/libhmap/src/hmap.c
new file mode 100644
index 0000000..d527516
--- /dev/null
+++ b/lib/libhmap/src/hmap.c
@@ -0,0 +1,320 @@
+#include "hmap.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+
+static inline size_t
+hmap_key_bucket(const struct hmap *map, struct hmap_key key)
+{
+ return map->hash(key) % map->bucketcnt;
+}
+
+int
+hmap_init(struct hmap *map, size_t size, hmap_hash_func hasher,
+ hmap_keycmp_func keycmp, const struct allocator *allocator)
+{
+ int rc;
+
+ LIBHMAP_ABORT_ON_ARGS(!size || !hasher || !keycmp || !allocator);
+
+ map->allocator = allocator;
+ map->keycmp = keycmp;
+ map->bucketcnt = size;
+ map->hash = hasher;
+ map->buckets = map->allocator->alloc(map->allocator,
+ sizeof(void *) * size, &rc);
+ if (!map->buckets) return -rc;
+ memset(map->buckets, 0, size * sizeof(void *));
+
+ return 0;
+}
+
+int
+hmap_deinit(struct hmap *map)
+{
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ hmap_clear(map);
+
+ return map->allocator->free(map->allocator, map->buckets);
+}
+
+struct hmap *
+hmap_alloc(size_t size, hmap_hash_func hasher,
+ hmap_keycmp_func keycmp, const struct allocator *allocator, int *_rc)
+{
+ struct hmap *map;
+ int *rc, stub;
+
+ LIBHMAP_ABORT_ON_ARGS(!allocator);
+
+ rc = _rc ? _rc : &stub;
+
+ map = allocator->alloc(allocator, sizeof(struct hmap), rc);
+ if (!map && _rc) *rc = -*rc;
+ if (!map) return NULL;
+
+ *rc = hmap_init(map, size, hasher, keycmp, allocator);
+ if (*rc) {
+ allocator->free(allocator, map);
+ return NULL;
+ }
+
+ return map;
+}
+
+int
+hmap_free(struct hmap *map)
+{
+ const struct allocator *allocator;
+ int rc;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ allocator = map->allocator;
+ rc = hmap_deinit(map);
+ if (rc) return rc;
+
+ rc = allocator->free(allocator, map);
+ if (rc) return -rc;
+
+ return 0;
+}
+
+int
+hmap_copy(const struct hmap *dst, const struct hmap *src)
+{
+ struct hmap_iter iter;
+ int rc;
+
+ LIBHMAP_ABORT_ON_ARGS(!dst || !src);
+
+ for (HMAP_ITER(src, iter)) {
+ rc = hmap_set(dst, iter.link->key, iter.link->value);
+ if (rc) return rc;
+ }
+
+ return 0;
+}
+
+void
+hmap_swap(struct hmap *m1, struct hmap *m2)
+{
+ struct hmap tmp;
+
+ LIBHMAP_ABORT_ON_ARGS(!m1 || !m2);
+
+ memcpy(&tmp, m1, sizeof(struct hmap));
+ memcpy(m1, m2, sizeof(struct hmap));
+ memcpy(m2, &tmp, sizeof(struct hmap));
+}
+
+void
+hmap_clear(const struct hmap *map)
+{
+ struct hmap_iter iter;
+ struct hmap_link *prev;
+ size_t i;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ prev = NULL;
+ for (HMAP_ITER(map, iter)) {
+ map->allocator->free(map->allocator, prev);
+ prev = iter.link;
+ }
+ map->allocator->free(map->allocator, prev);
+
+ for (i = 0; i < map->bucketcnt; i++)
+ map->buckets[i] = NULL;
+}
+
+struct hmap_link **
+hmap_link_get(const struct hmap *map, struct hmap_key key)
+{
+ struct hmap_link **iter, *link;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ iter = &map->buckets[hmap_key_bucket(map, key)];
+ while (*iter != NULL) {
+ link = *iter;
+ if (map->keycmp(link->key, key))
+ return iter;
+ iter = &(*iter)->next;
+ }
+
+ return NULL;
+}
+
+struct hmap_link **
+hmap_link_pos(const struct hmap *map, struct hmap_key key)
+{
+ struct hmap_link **iter, *link;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ iter = &map->buckets[hmap_key_bucket(map, key)];
+ while (*iter != NULL) {
+ link = *iter;
+ if (map->keycmp(link->key, key))
+ return iter;
+ iter = &(*iter)->next;
+ }
+
+ return iter;
+}
+
+struct hmap_link *
+hmap_link_pop(const struct hmap *map, struct hmap_link **link)
+{
+ struct hmap_link *tmp;
+
+ LIBHMAP_ABORT_ON_ARGS(!map || !link);
+
+ tmp = *link;
+ *link = (*link)->next;
+
+ return tmp;
+}
+
+struct hmap_link *
+hmap_link_alloc(const struct hmap *map,
+ struct hmap_key key, struct hmap_val value, int *rc)
+{
+ struct hmap_link *link;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ link = map->allocator->alloc(map->allocator,
+ sizeof(struct hmap_link), rc);
+ if (!link && rc) *rc = -*rc;
+ if (!link) return NULL;
+
+ link->key = key;
+ link->value = value;
+ link->next = NULL;
+
+ return link;
+}
+
+struct hmap_link *
+hmap_get(const struct hmap *map, struct hmap_key key)
+{
+ struct hmap_link **iter;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ iter = hmap_link_get(map, key);
+
+ return iter ? *iter : NULL;
+}
+
+int
+hmap_set(const struct hmap *map, struct hmap_key key, struct hmap_val value)
+{
+ struct hmap_link **iter;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ iter = hmap_link_pos(map, key);
+ if (!*iter) return HMAP_KEY_MISSING;
+
+ (*iter)->value = value;
+
+ return 0;
+}
+
+int
+hmap_rm(const struct hmap *map, struct hmap_key key)
+{
+ struct hmap_link *link, **linkp;
+ int rc;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ linkp = hmap_link_get(map, key);
+ if (!linkp) return HMAP_KEY_MISSING;
+ link = hmap_link_pop(map, linkp);
+
+ rc = map->allocator->free(map->allocator, link);
+ if (rc) return -rc;
+
+ return 0;
+}
+
+int
+hmap_add(const struct hmap *map, struct hmap_key key, struct hmap_val value)
+{
+ struct hmap_link **iter;
+ int rc;
+
+ LIBHMAP_ABORT_ON_ARGS(!map);
+
+ iter = hmap_link_pos(map, key);
+ if (*iter) return HMAP_KEY_EXISTS;
+
+ *iter = hmap_link_alloc(map, key, value, &rc);
+ if (!*iter) return rc;
+
+ return 0;
+}
+
+void
+hmap_iter_init(struct hmap_iter *iter)
+{
+ LIBHMAP_ABORT_ON_ARGS(!iter);
+
+ iter->bucket = 0;
+ iter->link = NULL;
+}
+
+bool
+hmap_iter_next(const struct hmap *map, struct hmap_iter *iter)
+{
+ size_t i;
+
+ LIBHMAP_ABORT_ON_ARGS(!map || !iter);
+
+ if (iter->link && iter->link->next) {
+ iter->link = iter->link->next;
+ return true;
+ }
+
+ i = iter->bucket + (iter->link ? 1 : 0);
+ for (; i < map->bucketcnt; i++) {
+ if (map->buckets[i]) {
+ iter->bucket = i;
+ iter->link = map->buckets[i];
+ return true;
+ }
+ }
+
+ iter->link = NULL;
+
+ return false;
+}
+
+uint32_t
+hmap_str_hash(struct hmap_key key)
+{
+ const char *c;
+ uint32_t hash;
+
+ LIBHMAP_ABORT_ON_ARGS(!key.p);
+
+ hash = 5381;
+ for (c = key.p; *c; c++)
+ hash = 33 * hash + (uint8_t) *c;
+
+ return hash;
+}
+
+bool
+hmap_str_keycmp(struct hmap_key k1, struct hmap_key k2)
+{
+ LIBHMAP_ABORT_ON_ARGS(!k1.p || !k2.p);
+
+ return !strcmp(k1.p, k2.p);
+}
diff --git a/lib/libhmap/src/test.c b/lib/libhmap/src/test.c
new file mode 100644
index 0000000..b248702
--- /dev/null
+++ b/lib/libhmap/src/test.c
@@ -0,0 +1,42 @@
+#include "hmap.h"
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define LIBHMAP_ERR(rc) errx(1, "libhmap: %s", \
+ rc < 0 ? strerror(-rc) : hmap_err[rc])
+
+static const char *hmap_err[] = {
+ HMAP_STRERR_INIT
+};
+
+int
+main(int argc, const char **argv)
+{
+ struct hmap hmap;
+ struct hmap_iter iter;
+ char *arg;
+ int i, rc;
+
+ rc = hmap_init(&hmap, 10, hmap_str_hash,
+ hmap_str_keycmp, &stdlib_heap_allocator);
+ if (rc) LIBHMAP_ERR(rc);
+
+ for (i = 1; i < argc; i++) {
+ arg = strdup(argv[i]);
+ rc = hmap_add(&hmap, (struct hmap_key) {.p = arg},
+ (struct hmap_val) {.i = i});
+ if (rc) LIBHMAP_ERR(rc);
+ }
+
+ for (HMAP_ITER(&hmap, iter)) {
+ printf("%s: %i\n", (char *)iter.link->key.p,
+ iter.link->value.i);
+ }
+
+ for (HMAP_ITER(&hmap, iter))
+ free(iter.link->key._p);
+ hmap_deinit(&hmap);
+}
diff --git a/lib/libtabular b/lib/libtabular
deleted file mode 160000
-Subproject f518cb2853fa164b4c4d3871da464c8f54fc600
diff --git a/lib/libtabular/.gitignore b/lib/libtabular/.gitignore
new file mode 100644
index 0000000..65056fc
--- /dev/null
+++ b/lib/libtabular/.gitignore
@@ -0,0 +1,8 @@
+compile_commands.json
+build
+build.rmk
+.cache
+.gdb_history
+vgcore*
+/.subgit
+/.subrepo
diff --git a/lib/libtabular/.gitmodules b/lib/libtabular/.gitmodules
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/libtabular/.gitmodules
diff --git a/lib/libtabular/.subgitrc b/lib/libtabular/.subgitrc
new file mode 100644
index 0000000..0ec8d87
--- /dev/null
+++ b/lib/libtabular/.subgitrc
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+declare -A subgit subgitinfo
+
+subgit[lib/liballoc]=1
+subgitinfo[lib/liballoc/remote]='git@sinitax.com:sinitax/liballoc-c'
+subgitinfo[lib/liballoc/branch]='master'
+subgitinfo[lib/liballoc/commit]='fd51196ac2d1bf1c34c8578146ef1bc66c1c8ec4'
+
diff --git a/lib/libtabular/build.jst b/lib/libtabular/build.jst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/libtabular/build.jst
diff --git a/lib/libtabular/build.rmk.tmpl b/lib/libtabular/build.rmk.tmpl
new file mode 100644
index 0000000..5f874ed
--- /dev/null
+++ b/lib/libtabular/build.rmk.tmpl
@@ -0,0 +1,70 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef LIBTABULAR_DEBUG
+#define DEBUG 1
+#endif
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include -I lib/liballoc/include #{OPT_CFLAGS}
+ #{EXTRA_CFLAGS} #{LIBTABULAR_EXTRA_CFLAGS}
+
+rule liba
+ #{CC} -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=libtabular.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ #{CC} -o $out $in $cflags -shared -Wl,-version-script libtabular.lds
+
+rule cc
+ #{CC} -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target lib/liballoc/build/liballoc.a
+ rmk lib/liballoc
+
+target build/libtabular.a
+ liba src/tabular.c src/util.c lib/liballoc/build/liballoc.a
+ | build include/tabular.h libtabular.api
+
+target build/libtabular.so
+ libso src/tabular.c src/util.c
+ | build include/tabular.h libtabular.lds
+
+target build/test
+ cc src/test.c build/libtabular.a lib/liballoc/build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ rmk clean
+ rmk -C lib/liballoc cleanall
+
+command install
+ install -m644 include/tabular.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+ install -m755 build/libtabular.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/libtabular.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/tabular.h"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libtabular.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libtabular.so"
+
+command all
+ rmk build/libtabular.a build/libtabular.so build/test
diff --git a/lib/libtabular/configure b/lib/libtabular/configure
new file mode 100755
index 0000000..b4dd05d
--- /dev/null
+++ b/lib/libtabular/configure
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+tmpl "$@" build.rmk.tmpl > build.rmk
+for lib in ./lib/*; do
+ pushd $lib
+ ./configure "$@"
+ popd
+done
diff --git a/lib/libtabular/include/tabular.h b/lib/libtabular/include/tabular.h
new file mode 100644
index 0000000..b3d1c54
--- /dev/null
+++ b/lib/libtabular/include/tabular.h
@@ -0,0 +1,133 @@
+#pragma once
+
+#include "allocator.h"
+
+#include <wchar.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <wctype.h>
+
+enum {
+ TABULAR_ALIGN_LEFT,
+ TABULAR_ALIGN_CENTER,
+ TABULAR_ALIGN_RIGHT
+};
+
+enum {
+ TABULAR_TRUNC,
+ TABULAR_WRAP,
+ TABULAR_WRAP_WORDAWARE
+};
+
+enum {
+ TABULAR_ENTRY_HIDDEN,
+ TABULAR_ENTRY_STR_SET,
+ TABULAR_ENTRY_HIDDEN_SET,
+};
+
+struct tabular_user {
+ union {
+ void *ptr;
+ size_t id;
+ };
+};
+
+struct tabular_entry {
+ char *str;
+ uint32_t flags;
+ uint32_t ulen;
+};
+
+struct tabular_row {
+ struct tabular_user user;
+ struct tabular_entry *entries;
+ struct tabular_row *next;
+};
+
+struct tabular_col {
+ struct tabular_user user;
+
+ /* column name displayed in header */
+ const char *name;
+
+ /* create str entry to display from user objects */
+ char *(*to_str)(const struct tabular_user *user_row,
+ const struct tabular_user *user_col);
+
+ /* only show content if atleast one row is not 'hidden' */
+ bool (*is_hidden)(const struct tabular_user *user_row,
+ const struct tabular_user *user_col);
+
+ /* size restrictions */
+ size_t minwidth, maxwidth;
+
+ /* whitespace padding */
+ size_t lpad, rpad;
+
+ /* text alignment */
+ int align;
+
+ /* content packing strategy */
+ int strategy;
+
+ /* omiting column due to col contraints disallowed */
+ bool essential;
+
+ /* reducing length due to col contraints allowed */
+ bool squashable;
+};
+
+struct tabular_cfg {
+ struct tabular_user user;
+
+ const struct tabular_col *columns;
+ size_t column_count;
+
+ /* color mode: 1, 16, 256 */
+ int colors;
+
+ /* fit rows to output size */
+ bool fit_rows;
+
+ /* amount of available lines to skip */
+ size_t skip_lines;
+
+ /* output dimensions */
+ size_t outw, outh;
+
+ /* entry separators */
+ const char *hsep, *vsep, *xsep;
+
+ /* total lpad, rpad */
+ size_t lpad, rpad;
+
+ /* lazy load rows */
+ struct tabular_row *(*row_gen)(const struct tabular_user *user);
+
+ /* style printing hook for header, separators and content */
+ bool (*print_style)(FILE *file, const struct tabular_cfg *cfg,
+ const struct tabular_row *row, const struct tabular_col *col);
+
+ const struct allocator *allocator;
+};
+
+struct tabular_stats {
+ bool rows_truncated;
+ bool cols_truncated;
+ size_t rows_displayed;
+ size_t lines_used;
+};
+
+int tabular_format(FILE *file, const struct tabular_cfg *cfg,
+ struct tabular_stats *stat, struct tabular_row **rows);
+
+void tabular_load_row_entry_hidden(const struct tabular_cfg *cfg,
+ struct tabular_row *row, size_t col);
+void tabular_load_row_entry_str(const struct tabular_cfg *cfg,
+ struct tabular_row *row, size_t col);
+
+struct tabular_row *tabular_alloc_row(const struct tabular_cfg *cfg,
+ int *error, struct tabular_user user);
+int tabular_free_rows(const struct tabular_cfg *cfg, struct tabular_row *rows);
diff --git a/lib/libtabular/lib/liballoc/.gitignore b/lib/libtabular/lib/liballoc/.gitignore
new file mode 100644
index 0000000..b6a670a
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/.gitignore
@@ -0,0 +1,7 @@
+compile_commands.json
+build
+build.rmk
+.cache
+vgcore*
+.gdb_history
+test
diff --git a/lib/libtabular/lib/liballoc/LICENSE b/lib/libtabular/lib/liballoc/LICENSE
new file mode 100644
index 0000000..361f116
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Louis Burda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/libtabular/lib/liballoc/Makefile b/lib/libtabular/lib/liballoc/Makefile
new file mode 100644
index 0000000..dab8818
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/Makefile
@@ -0,0 +1,45 @@
+PREFIX ?= /usr/local
+LIBDIR ?= /lib
+INCLDIR ?= /include
+
+CFLAGS = -I include
+
+ifeq "$(DEBUG)" "1"
+CFLAGS += -Og -g
+else
+CFLAGS += -O2
+endif
+
+all: build/liballoc.so build/liballoc.a build/test
+
+clean:
+ rm -rf build
+
+cleanall: clean
+
+build:
+ mkdir build
+
+build/liballoc.a: src/allocator.c | build
+ $(CC) -o build/tmp.o src/allocator.c $(CFLAGS) -r
+ objcopy --keep-global-symbols=liballoc.api build/tmp.o build/fixed.o
+ ar rcs $@ build/fixed.o
+
+build/liballoc.so: src/allocator.c include/allocator.h | build
+ $(CC) -o $@ src/allocator.c $(CFLAGS) \
+ -shared -Wl,-version-script liballoc.lds
+
+build/test: src/test.c build/liballoc.a | build
+ $(CC) -o $@ $^ -I include
+
+install:
+ install -m755 include/allocator.h -t "$(DESTDIR)$(PREFIX)$(INCLDIR)"
+ install -m755 build/liballoc.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+ install -m755 build/liballoc.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)$(INCLDIR)/allocator.h"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.a"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.so"
+
+.PHONY: all clean cleanall install uninstall
diff --git a/lib/libtabular/lib/liballoc/build.jst b/lib/libtabular/lib/liballoc/build.jst
new file mode 100644
index 0000000..f590d49
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/build.jst
@@ -0,0 +1,51 @@
+
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include -O2
+
+rule liba
+ gcc -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=liballoc.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ gcc -o $out $in $cflags -shared -Wl,-version-script liballoc.lds
+
+rule cc
+ gcc -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target build/liballoc.a
+ liba src/allocator.c | include/allocator.h build
+
+target build/liballoc.so
+ libso src/allocator.c | include/allocator.h build
+
+target build/test
+ cc src/test.c build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ just clean
+
+command install
+ install -m755 build/liballoc.a -t "/usr/local/lib"
+ install -m755 build/liballoc.so -t "/usr/local/lib"
+ install -m644 include/allocator.h -t "/usr/local/include"
+
+command uninstall
+ rm -f "/usr/local/lib/liballoc.a"
+ rm -f "/usr/local/lib/liballoc.so"
+ rm -f "/usr/local/include/allocator.h"
+
+command all
+ just build/liballoc.a build/liballoc.so build/test
+
diff --git a/lib/libtabular/lib/liballoc/build.rmk.tmpl b/lib/libtabular/lib/liballoc/build.rmk.tmpl
new file mode 100644
index 0000000..410749d
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/build.rmk.tmpl
@@ -0,0 +1,60 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include #{OPT_CFLAGS} #{EXTRA_CFLAGS}
+
+rule liba
+ gcc -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=liballoc.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ gcc -o $out $in $cflags -shared -Wl,-version-script liballoc.lds
+
+rule cc
+ gcc -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target build/liballoc.a
+ liba src/allocator.c | include/allocator.h build
+
+target build/liballoc.so
+ libso src/allocator.c | include/allocator.h build
+
+target build/test
+ cc src/test.c build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ just clean
+
+command install
+ install -m755 build/liballoc.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/liballoc.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m644 include/allocator.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.so"
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/allocator.h"
+
+command all
+ just build/liballoc.a build/liballoc.so build/test
+
diff --git a/lib/libtabular/lib/liballoc/common.mk b/lib/libtabular/lib/liballoc/common.mk
new file mode 100644
index 0000000..fe00d79
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/common.mk
@@ -0,0 +1,9 @@
+LIBALLOC_A = build/liballoc.a
+LIBALLOC_A_SRC = src/allocator.c
+LIBALLOC_A_DEP = $(LIBALLOC_A_SRC) include/allocator.h
+LIBALLOC_A_SRCDEP = $(LIBALLOC_A_DEP)
+
+LIBALLOC_SO = build/liballoc.so
+LIBALLOC_SO_SRC = src/allocator.c
+LIBALLOC_SO_DEP = $(LIBALLOC_SO_SRC) include/allocator.h
+LIBALLOC_SO_SRCDEP = $(LIBALLOC_SO_DEP)
diff --git a/lib/libtabular/lib/liballoc/configure b/lib/libtabular/lib/liballoc/configure
new file mode 100755
index 0000000..16d5220
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/configure
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -ex
+
+tmpl "$@" build.rmk.tmpl > build.rmk
diff --git a/lib/libtabular/lib/liballoc/include/allocator.h b/lib/libtabular/lib/liballoc/include/allocator.h
new file mode 100644
index 0000000..fc9a13d
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/include/allocator.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <stddef.h>
+
+struct allocator {
+ void *(*alloc)(const struct allocator *allocator,
+ size_t size, int *rc);
+ void *(*realloc)(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+ int (*free)(const struct allocator *allocator, void *p);
+};
+
+struct strict_allocator {
+ const struct allocator *allocator_ul;
+ struct allocator allocator;
+};
+
+void strict_allocator_init(struct strict_allocator *strict_allocator_init,
+ const struct allocator *allocator_ul);
+
+extern const struct allocator stdlib_heap_allocator;
+extern const struct allocator stdlib_strict_heap_allocator;
diff --git a/lib/libtabular/lib/liballoc/liballoc.api b/lib/libtabular/lib/liballoc/liballoc.api
new file mode 100644
index 0000000..e875b2b
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/liballoc.api
@@ -0,0 +1,4 @@
+strict_allocator_init
+
+stdlib_heap_allocator
+stdlib_strict_heap_allocator
diff --git a/lib/libtabular/lib/liballoc/liballoc.lds b/lib/libtabular/lib/liballoc/liballoc.lds
new file mode 100644
index 0000000..07e5fca
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/liballoc.lds
@@ -0,0 +1,7 @@
+LIBALLOC_2.1 {
+ global:
+ strict_allocator_init;
+ stdlib_heap_allocator;
+ stdlib_strict_heap_allocator;
+ local: *;
+};
diff --git a/lib/libtabular/lib/liballoc/src/allocator.c b/lib/libtabular/lib/liballoc/src/allocator.c
new file mode 100644
index 0000000..eb6bc6d
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/src/allocator.c
@@ -0,0 +1,150 @@
+#include "allocator.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+static void *stdlib_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_heap_free(const struct allocator *allocator, void *p);
+
+static void *stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_strict_heap_free(const struct allocator *allocator, void *p);
+
+const struct allocator stdlib_heap_allocator = {
+ .alloc = stdlib_heap_alloc,
+ .realloc = stdlib_heap_realloc,
+ .free = stdlib_heap_free
+};
+
+const struct allocator stdlib_strict_heap_allocator = {
+ .alloc = stdlib_strict_heap_alloc,
+ .realloc = stdlib_strict_heap_realloc,
+ .free = stdlib_strict_heap_free
+};
+
+void *
+stdlib_heap_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (rc && !p) *rc = errno;
+
+ return p;
+}
+
+void *
+stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (rc && !np) *rc = errno;
+
+ return np;
+}
+
+int
+stdlib_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+stdlib_strict_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+strict_allocator_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *p;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ p = strict_allocator->allocator_ul->alloc(
+ strict_allocator->allocator_ul, size, rc);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+strict_allocator_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *np;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ np = strict_allocator->allocator_ul->realloc(
+ strict_allocator->allocator_ul, p, size, rc);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+strict_allocator_free(const struct allocator *allocator, void *p)
+{
+ const struct strict_allocator *strict_allocator;
+ int rc;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ rc = strict_allocator->allocator_ul->free(
+ strict_allocator->allocator_ul, p);
+ if (rc) abort();
+
+ return 0;
+}
+
+void
+strict_allocator_init(struct strict_allocator *strict_allocator,
+ const struct allocator *allocator_ul)
+{
+ strict_allocator->allocator_ul = allocator_ul;
+ strict_allocator->allocator.alloc = strict_allocator_alloc;
+ strict_allocator->allocator.realloc = strict_allocator_realloc;
+ strict_allocator->allocator.free = strict_allocator_free;
+}
diff --git a/lib/libtabular/lib/liballoc/src/test.c b/lib/libtabular/lib/liballoc/src/test.c
new file mode 100644
index 0000000..7b3ee44
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/src/test.c
@@ -0,0 +1,22 @@
+#include "allocator.h"
+
+#include <err.h>
+#include <string.h>
+#include <stdlib.h>
+
+const struct allocator *ga = &stdlib_heap_allocator;
+
+int
+main(int argc, const char **argv)
+{
+ struct test *test;
+ int rc;
+
+ if (argc <= 1) exit(1);
+
+ test = ga->alloc(ga, strtoull(argv[1], NULL, 10), &rc);
+ if (!test) errx(1, "alloc: %s", strerror(rc));
+
+ rc = ga->free(ga, test);
+ if (rc) errx(1, "free: %s", strerror(rc));
+}
diff --git a/lib/libtabular/libtabular.api b/lib/libtabular/libtabular.api
new file mode 100644
index 0000000..79ca84a
--- /dev/null
+++ b/lib/libtabular/libtabular.api
@@ -0,0 +1,7 @@
+tabular_format
+
+tabular_load_row_entry_str
+tabular_load_row_entry_hidden
+
+tabular_alloc_row
+tabular_free_rows
diff --git a/lib/libtabular/libtabular.lds b/lib/libtabular/libtabular.lds
new file mode 100644
index 0000000..5b1a674
--- /dev/null
+++ b/lib/libtabular/libtabular.lds
@@ -0,0 +1,7 @@
+LIBTABULAR_1.0 {
+ global:
+ tabular_format;
+ tabular_alloc_row;
+ tabular_free_rows;
+ local: *;
+};
diff --git a/lib/libtabular/src/tabular.c b/lib/libtabular/src/tabular.c
new file mode 100644
index 0000000..88fab17
--- /dev/null
+++ b/lib/libtabular/src/tabular.c
@@ -0,0 +1,695 @@
+#define _XOPEN_SOURCE
+
+#include "tabular.h"
+#include "util.h"
+
+#include <sys/ioctl.h>
+#include <wchar.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#define BIT(i) (1U << (i))
+
+struct col_state {
+ size_t width;
+ size_t written;
+ size_t maxlen;
+ size_t lpad, rpad;
+ bool hidden;
+};
+
+struct fmt_state {
+ struct col_state *columns;
+ size_t column_count;
+
+ size_t hseplen;
+ size_t vseplen;
+ size_t xseplen;
+ size_t mseplen;
+
+ size_t line_limit;
+ size_t row_limit;
+
+ bool header_line;
+};
+
+static void calc_word_aware(struct tabular_entry *entry, size_t maxwidth,
+ size_t *offset, size_t *width, size_t *lines);
+static size_t recalc_col_word_aware(const struct tabular_cfg *cfg,
+ struct tabular_row *rows, size_t limit,
+ size_t col, size_t maxwidth);
+static size_t calc_row_width(struct fmt_state *fmt);
+static size_t calc_output_lines(const struct tabular_cfg *cfg,
+ struct fmt_state *fmt, struct tabular_row *rows, size_t limit);
+static bool recalc_cols(const struct tabular_cfg *cfg,
+ struct tabular_stats *stats, struct fmt_state *fmt,
+ struct tabular_row *rows, size_t limit);
+
+static int calc_params_row(const struct tabular_cfg *cfg,
+ struct tabular_stats *stats, struct tabular_row *rows,
+ struct tabular_row *row, struct fmt_state *fmt, size_t limit);
+static int calc_params(const struct tabular_cfg *cfg,
+ struct tabular_stats *stat, struct tabular_row **rows,
+ struct fmt_state *fmt);
+
+static void output_header(FILE *file, const struct tabular_cfg *cfg,
+ struct tabular_stats *stat, const struct fmt_state *fmt);
+static void output_row(FILE *file, const struct tabular_cfg *cfg,
+ struct tabular_stats *stat, const struct tabular_row *row,
+ struct fmt_state *fmt);
+static int output_rows(FILE *file, const struct tabular_cfg *cfg,
+ struct tabular_stats *stats, struct tabular_row *rows,
+ struct fmt_state *fmt);
+
+const char *
+calc_word_aware_line(const char *str, size_t maxwidth,
+ size_t *out_offset, size_t *out_width)
+{
+ const char *sep, *nstr;
+ size_t width, nwidth;
+ size_t offset, wlen;
+
+ if (!str) {
+ if (out_offset) *out_offset = 0;
+ if (out_width) *out_width = 0;
+ return NULL;
+ }
+
+ offset = strspn(str, " \v\t\r\n");
+ str += offset;
+
+ nstr = str;
+ nwidth = width = 0;
+ while (nwidth <= maxwidth) {
+ width = nwidth;
+ str = nstr;
+ if (!str) break;
+ sep = strchr(str, ' ');
+ if (!sep) {
+ wlen = u8strlen(str);
+ nstr = NULL;
+ } else {
+ wlen = u8strnlen(str, (size_t) (sep - str));
+ nstr = sep + 1;
+ }
+ nwidth = width + (width > 0) + wlen;
+ }
+
+ if (out_offset) *out_offset = offset;
+ if (out_width) {
+ /* single word > maxwidth */
+ if (!width && nwidth) {
+ *out_width = maxwidth;
+ str = nstr; /* skip single word */
+ } else {
+ *out_width = width;
+ }
+ }
+
+ return str;
+}
+
+void
+calc_word_aware(struct tabular_entry *entry, size_t maxwidth,
+ size_t *out_offset, size_t *out_width, size_t *out_lines)
+{
+ const char *str;
+ size_t width, mwidth;
+ size_t lines;
+
+ if (out_offset) *out_offset = 0;
+
+ lines = 0;
+ mwidth = 0;
+ str = entry->str;
+ while (str) {
+ str = calc_word_aware_line(str, maxwidth,
+ str == entry->str ? out_offset : NULL, &width);
+ lines++;
+ mwidth = MAX(mwidth, width);
+ }
+
+ if (out_width) *out_width = mwidth;
+ if (out_lines) *out_lines = lines;
+}
+
+size_t
+recalc_col_word_aware(const struct tabular_cfg *cfg,
+ struct tabular_row *rows, size_t limit, size_t col,
+ size_t maxwidth)
+{
+ size_t off, wwidth, max_wwidth, rowcnt;
+ struct tabular_row *row;
+
+ max_wwidth = cfg->columns[col].minwidth;
+
+ rowcnt = 0;
+ for (row = rows; row && rowcnt < limit; row = row->next) {
+ calc_word_aware(&row->entries[col],
+ maxwidth, &off, &wwidth, NULL);
+ max_wwidth = MAX(max_wwidth, wwidth);
+ rowcnt += 1;
+ }
+
+ return max_wwidth;
+}
+
+size_t
+calc_row_width(struct fmt_state *fmt)
+{
+ size_t sum, i;
+
+ sum = 0;
+ for (i = 0; i < fmt->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+ sum += fmt->columns[i].width + (sum ? fmt->hseplen : 0);
+ }
+
+ return sum;
+}
+
+size_t
+calc_output_lines(const struct tabular_cfg *cfg, struct fmt_state *fmt,
+ struct tabular_row *rows, size_t limit)
+{
+ struct tabular_row *row;
+ size_t row_index, row_lines;
+ size_t entry_lines;
+ size_t lines, i;
+ size_t width;
+
+ lines = 0;
+ row_index = 0;
+ for (row = rows; row; row = row->next) {
+ if (row_index == limit) break;
+
+ row_lines = 0;
+ for (i = 0; i < cfg->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+ width = fmt->columns[i].width
+ - fmt->columns[i].lpad - fmt->columns[i].rpad;
+ switch (cfg->columns[i].strategy) {
+ case TABULAR_TRUNC:
+ entry_lines = 1;
+ break;
+ case TABULAR_WRAP:
+ entry_lines = CEILDIV(row->entries[i].ulen, width);
+ break;
+ case TABULAR_WRAP_WORDAWARE:
+ calc_word_aware(&row->entries[i],
+ width, NULL, NULL, &entry_lines);
+ break;
+ }
+ row_lines = MAX(row_lines, entry_lines);
+ }
+ lines += row_lines;
+
+ row_index += 1;
+ }
+
+ return lines;
+}
+
+bool
+recalc_cols(const struct tabular_cfg *cfg, struct tabular_stats *stats,
+ struct fmt_state *fmt, struct tabular_row *rows, size_t limit)
+{
+ size_t width, fullwidth, remaining;
+ ssize_t i;
+
+ fullwidth = cfg->outw - cfg->lpad - cfg->rpad;
+
+ /* reset widths to minimum requirement */
+ for (i = 0; i < cfg->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+ if (cfg->columns[i].squashable) {
+ fmt->columns[i].width = cfg->columns[i].minwidth;
+ } else {
+ width = MIN(fmt->columns[i].maxlen,
+ cfg->columns[i].maxwidth - cfg->columns[i].lpad
+ - cfg->columns[i].rpad);
+ if (cfg->columns[i].strategy == TABULAR_WRAP_WORDAWARE)
+ width = recalc_col_word_aware(cfg, rows,
+ limit, (size_t) i, width);
+ fmt->columns[i].width = width + cfg->columns[i].lpad
+ + cfg->columns[i].rpad;
+ }
+ }
+
+ /* could not fit all necessary columns at minimum width */
+ width = calc_row_width(fmt);
+ while (width > fullwidth) {
+ /* remove non-essential columns */
+ for (i = ((ssize_t) cfg->column_count) - 1; i >= 0; i--) {
+ if (!cfg->columns[i].essential
+ && !fmt->columns[i].hidden) {
+ fmt->columns[i].hidden = true;
+ break;
+ }
+ }
+
+ /* failed to remove any more */
+ if (i < 0) return false;
+
+ stats->cols_truncated = true;
+ width = calc_row_width(fmt);
+ }
+
+ /* redistribute excess width to columns, left to right */
+ remaining = fullwidth - width;
+ for (i = 0; remaining > 0 && i < cfg->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+ if (!cfg->columns[i].squashable) continue;
+ width = MIN(fmt->columns[i].maxlen, cfg->columns[i].maxwidth
+ - fmt->columns[i].lpad - fmt->columns[i].rpad);
+ width = MIN(width, fmt->columns[i].width + remaining
+ - fmt->columns[i].lpad - fmt->columns[i].rpad);
+ if (cfg->columns[i].strategy == TABULAR_WRAP_WORDAWARE)
+ width = recalc_col_word_aware(cfg, rows,
+ limit, (size_t) i, width);
+ width = MAX(width + fmt->columns[i].lpad + fmt->columns[i].rpad,
+ fmt->columns[i].width);
+ remaining -= width - fmt->columns[i].width;
+ fmt->columns[i].width = width;
+ }
+
+ return true;
+}
+
+int
+calc_params_row(const struct tabular_cfg *cfg, struct tabular_stats *stats,
+ struct tabular_row *rows, struct tabular_row *row,
+ struct fmt_state *fmt, size_t limit)
+{
+ struct colinfo *copy;
+ size_t i, height;
+ int rc;
+
+ /* make copy of current width in case the next row does not fit */
+ copy = cfg->allocator->alloc(cfg->allocator,
+ sizeof(struct col_state) * cfg->column_count, &rc);
+ if (!copy) return -rc;
+ memcpy(copy, fmt->columns, sizeof(struct col_state) * cfg->column_count);
+
+ /* unhide cols */
+ for (i = 0; i < cfg->column_count; i++) {
+ tabular_load_row_entry_hidden(cfg, row, i);
+ if (fmt->columns[i].hidden) {
+ fmt->columns[i].hidden =
+ (row->entries[i].flags & BIT(TABULAR_ENTRY_HIDDEN));
+ }
+ if (fmt->columns[i].hidden) continue;
+ }
+
+ /* update maxlen for visible cols */
+ for (i = 0; i < cfg->column_count; i++) {
+ tabular_load_row_entry_str(cfg, row, i);
+ fmt->columns[i].maxlen = MAX(fmt->columns[i].maxlen,
+ row->entries[i].ulen);
+ }
+
+ /* recalc column sizes to fit new column */
+ if (!recalc_cols(cfg, stats, fmt, rows, limit))
+ goto undo;
+
+ height = calc_output_lines(cfg, fmt, rows, limit);
+
+ /* check the line limit if applicable */
+ if (fmt->line_limit > 0 && height > fmt->line_limit)
+ goto undo;
+
+ cfg->allocator->free(cfg->allocator, copy);
+
+ return 0;
+
+undo:
+ memcpy(fmt->columns, copy, sizeof(struct col_state) * fmt->column_count);
+ cfg->allocator->free(cfg->allocator, copy);
+
+ return 1;
+}
+
+int
+calc_params(const struct tabular_cfg *cfg, struct tabular_stats *stats,
+ struct tabular_row **rows, struct fmt_state *fmt)
+{
+ struct tabular_row **row;
+ ssize_t first, last;
+ size_t i;
+ int rc;
+
+ fmt->header_line = ((cfg->vsep != NULL && *cfg->vsep)
+ && (cfg->xsep != NULL && *cfg->xsep));
+
+ fmt->line_limit = 0;
+ if (cfg->fit_rows) {
+ fmt->line_limit = MAX(0, cfg->outh
+ - cfg->skip_lines - 1 - fmt->header_line);
+ }
+
+ fmt->hseplen = u8strlen(cfg->hsep);
+ fmt->vseplen = u8strlen(cfg->vsep);
+ fmt->xseplen = u8strlen(cfg->xsep);
+ fmt->mseplen = MAX(fmt->hseplen, fmt->xseplen);
+
+ fmt->column_count = cfg->column_count;
+ fmt->columns = cfg->allocator->alloc(cfg->allocator,
+ fmt->column_count * sizeof(struct col_state), &rc);
+ if (!fmt->columns) return -rc;
+
+ for (i = 0; i < cfg->column_count; i++) {
+ fmt->columns[i].lpad = cfg->columns[i].lpad;
+ fmt->columns[i].rpad = cfg->columns[i].rpad;
+ fmt->columns[i].hidden = true;
+ fmt->columns[i].width = cfg->columns[i].minwidth;
+ fmt->columns[i].maxlen = u8strlen(cfg->columns[i].name)
+ + fmt->columns[i].lpad + fmt->columns[i].rpad;
+ if (fmt->columns[i].maxlen > cfg->columns[i].minwidth)
+ return 1;
+ fmt->columns[i].written = 0;
+ }
+
+ fmt->row_limit = 0;
+
+ if (!*rows && cfg->row_gen) *rows = cfg->row_gen(&cfg->user);
+ if (!*rows) return 0;
+
+ if (!recalc_cols(cfg, stats, fmt, *rows, 0)) {
+ stats->cols_truncated = true;
+ stats->rows_truncated = true;
+ return 0;
+ }
+
+ for (row = rows; ; row = &(*row)->next) {
+ if (!*row && cfg->row_gen) *row = cfg->row_gen(&cfg->user);
+ if (!*row) break;
+ rc = calc_params_row(cfg, stats, *rows,
+ *row, fmt, fmt->row_limit + 1);
+ if (rc < 0) return rc;
+ if (rc > 0) {
+ stats->rows_truncated = true;
+ break;
+ }
+ fmt->row_limit++;
+ }
+
+ first = last = -1;
+ for (i = 0; i < cfg->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+ if (first < 0) first = (ssize_t) i;
+ last = (ssize_t) i;
+ }
+
+ if (first >= 0 && last >= 0) {
+ fmt->columns[first].lpad += cfg->lpad;
+ fmt->columns[first].width += cfg->lpad;
+ fmt->columns[last].rpad += cfg->rpad;
+ fmt->columns[last].width += cfg->rpad;
+ }
+
+ return 0;
+}
+
+void
+output_header(FILE *file, const struct tabular_cfg *cfg,
+ struct tabular_stats *stats, const struct fmt_state *fmt)
+{
+ size_t i, k, width;
+ bool dirty, first;
+
+ first = true;
+ for (i = 0; i < cfg->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+ if (!first) {
+ dirty = cfg->print_style(file, cfg, NULL, NULL);
+ fprintf(file, "%s%*.s", cfg->hsep,
+ (int) (fmt->mseplen - fmt->hseplen), "");
+ if (dirty) fprintf(file, "\x1b[0m");
+ }
+ first = false;
+
+ width = fmt->columns[i].width
+ - fmt->columns[i].lpad - fmt->columns[i].rpad;
+
+ dirty = cfg->print_style(file, cfg,
+ NULL, &cfg->columns[i]);
+ print_pad(file, fmt->columns[i].lpad);
+ print_left(file, cfg->columns[i].name, width, width);
+ print_pad(file, fmt->columns[i].rpad);
+ if (dirty) fprintf(file, "\x1b[0m");
+ }
+
+ fprintf(file, "\n");
+ stats->lines_used += 1;
+
+ if (fmt->header_line) {
+ first = true;
+ dirty = cfg->print_style(file, cfg, NULL, NULL);
+ for (i = 0; i < cfg->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+ if (!first) {
+ fprintf(file, "%s%*.s", cfg->xsep,
+ (int) (fmt->mseplen - fmt->xseplen), "");
+ }
+ for (k = 0; k < fmt->columns[i].width; k++)
+ fprintf(file, "%s", cfg->vsep);
+ first = false;
+ }
+ if (dirty) fprintf(file, "\x1b[0m");
+ fprintf(file, "\n");
+ stats->lines_used += 1;
+ }
+}
+
+void
+output_row(FILE *file, const struct tabular_cfg *cfg,
+ struct tabular_stats *stat, const struct tabular_row *row,
+ struct fmt_state *fmt)
+{
+ size_t wwidth, padwidth, outlen;
+ size_t i, off, width;
+ bool first, done, dirty;
+ char *entry;
+
+ for (i = 0; i < cfg->column_count; i++) {
+ fmt->columns[i].written = 0;
+
+ if (cfg->columns[i].strategy == TABULAR_TRUNC) {
+ width = fmt->columns[i].width
+ - fmt->columns[i].lpad - fmt->columns[i].rpad;
+ if (row->entries[i].ulen > width) {
+ width = u8rawlen(row->entries[i].str, width - 2);
+ row->entries[i].str[width] = '.';
+ row->entries[i].str[width+1] = '.';
+ row->entries[i].str[width+2] = '\0';
+ }
+ }
+ }
+
+ do {
+ done = true;
+ first = true;
+ for (i = 0; i < cfg->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+
+ if (!first) {
+ dirty = cfg->print_style(file, cfg, NULL, NULL);
+ fprintf(file, "%s", cfg->hsep);
+ if (dirty) fprintf(file, "\x1b[0m");
+ }
+ first = false;
+
+ if (!row->entries[i].str) {
+ print_pad(file, fmt->columns[i].width);
+ continue;
+ }
+
+ entry = row->entries[i].str + fmt->columns[i].written;
+
+ dirty = cfg->print_style(file, cfg, row, &cfg->columns[i]);
+
+ off = 0;
+ width = fmt->columns[i].width
+ - fmt->columns[i].lpad - fmt->columns[i].rpad;
+ padwidth = width;
+ if (cfg->columns[i].strategy == TABULAR_WRAP_WORDAWARE) {
+ calc_word_aware_line(entry, width, &off, &wwidth);
+ entry += off;
+ width = wwidth;
+ }
+
+ print_pad(file, fmt->columns[i].lpad);
+ if (cfg->columns[i].align == TABULAR_ALIGN_LEFT) {
+ print_left(file, entry, width, padwidth);
+ } else if (cfg->columns[i].align == TABULAR_ALIGN_RIGHT) {
+ print_right(file, entry, width, padwidth);
+ } else {
+ print_center(file, entry, width, padwidth);
+ }
+ print_pad(file, fmt->columns[i].rpad);
+
+ if (dirty) fprintf(file, "\x1b[0m");
+
+ outlen = MIN(u8strlen(entry), width + off);
+ fmt->columns[i].written += u8rawlen(entry, outlen);
+
+ /* check if anything other than whitespace left */
+ entry += u8rawlen(entry, outlen);
+ if (strspn(entry, " \t\v\r\n") != u8strlen(entry))
+ done = false;
+ }
+ fprintf(file, "\n");
+ stat->lines_used += 1;
+ } while (!done);
+
+ stat->rows_displayed += 1;
+}
+
+int
+output_rows(FILE *file, const struct tabular_cfg *cfg,
+ struct tabular_stats *stats, struct tabular_row *rows,
+ struct fmt_state *fmt)
+{
+ struct tabular_row *row;
+ size_t count;
+
+ if (fmt->row_limit == 0) return 0;
+
+ output_header(file, cfg, stats, fmt);
+
+ count = 0;
+ for (row = rows; row; row = row->next) {
+ if (count == fmt->row_limit)
+ break;
+ output_row(file, cfg, stats, row, fmt);
+ count += 1;
+ }
+
+ return 0;
+}
+
+int
+tabular_format(FILE *file, const struct tabular_cfg *cfg,
+ struct tabular_stats *stats, struct tabular_row **rows)
+{
+ struct fmt_state fmt;
+ size_t i;
+ int rc;
+
+ stats->lines_used = 0;
+ stats->rows_displayed = 0;
+ stats->rows_truncated = false;
+ stats->cols_truncated = false;
+
+ if (!rows) return 1;
+
+ if (!cfg->column_count) return 0;
+
+ for (i = 0; i < cfg->column_count; i++) {
+ if (cfg->columns[i].minwidth > cfg->columns[i].maxwidth)
+ return 1;
+ if (cfg->columns[i].lpad + cfg->columns[i].rpad
+ >= cfg->columns[i].minwidth)
+ return 1;
+ if (cfg->columns[i].strategy != TABULAR_TRUNC
+ && cfg->columns[i].strategy != TABULAR_WRAP
+ && cfg->columns[i].strategy != TABULAR_WRAP_WORDAWARE)
+ return 1;
+ }
+
+ rc = calc_params(cfg, stats, rows, &fmt);
+ if (rc) return rc;
+
+ rc = output_rows(file, cfg, stats, *rows, &fmt);
+ if (rc) return rc;
+
+ rc = cfg->allocator->free(cfg->allocator, fmt.columns);
+ if (rc) return -rc;
+
+ return 0;
+}
+
+struct tabular_row *
+tabular_alloc_row(const struct tabular_cfg *cfg,
+ int *rc, struct tabular_user user)
+{
+ struct tabular_row *row;
+
+ row = cfg->allocator->alloc(cfg->allocator,
+ sizeof(struct tabular_row), rc);
+ if (!row && rc) *rc = -*rc;
+ if (!row) return NULL;
+
+ row->next = NULL;
+ row->user = user;
+ row->entries = cfg->allocator->alloc(cfg->allocator,
+ sizeof(struct tabular_entry) * cfg->column_count, rc);
+ if (!row->entries && rc) *rc = -*rc;
+ if (!row->entries) return NULL;
+ memset(row->entries, 0, sizeof(struct tabular_entry) * cfg->column_count);
+
+ return row;
+}
+
+int
+tabular_free_rows(const struct tabular_cfg *cfg, struct tabular_row *rows)
+{
+ struct tabular_row *iter, *next;
+ size_t i;
+ int rc;
+
+ for (iter = rows; iter; iter = next) {
+ next = iter->next;
+ for (i = 0; i < cfg->column_count; i++) {
+ rc = cfg->allocator->free(cfg->allocator,
+ iter->entries[i].str);
+ if (rc) return -rc;
+ }
+ rc = cfg->allocator->free(cfg->allocator, iter->entries);
+ if (rc) return -rc;
+ rc = cfg->allocator->free(cfg->allocator, iter);
+ if (rc) return -rc;
+ }
+
+ return 0;
+}
+
+void
+tabular_load_row_entry_hidden(const struct tabular_cfg *cfg,
+ struct tabular_row *row, size_t col)
+{
+ bool hidden;
+
+ if (row->entries[col].flags & BIT(TABULAR_ENTRY_HIDDEN_SET)) return;
+
+ if (cfg->columns[col].is_hidden) {
+ hidden = cfg->columns[col].is_hidden(
+ &row->user, &cfg->columns[col].user);
+ } else {
+ hidden = false;
+ }
+
+ if (hidden) {
+ row->entries[col].flags |= BIT(TABULAR_ENTRY_HIDDEN);
+ } else {
+ row->entries[col].flags &= ~BIT(TABULAR_ENTRY_HIDDEN);
+ }
+
+ row->entries[col].flags |= BIT(TABULAR_ENTRY_HIDDEN_SET);
+}
+
+void
+tabular_load_row_entry_str(const struct tabular_cfg *cfg,
+ struct tabular_row *row, size_t col)
+{
+ if (row->entries[col].flags & BIT(TABULAR_ENTRY_STR_SET)) return;
+
+ row->entries[col].str = cfg->columns[col].to_str(
+ &row->user, &cfg->columns[col].user);
+ row->entries[col].ulen = (uint32_t) u8strlen(row->entries[col].str);
+ row->entries[col].flags |= BIT(TABULAR_ENTRY_STR_SET);
+}
diff --git a/lib/libtabular/src/test.c b/lib/libtabular/src/test.c
new file mode 100644
index 0000000..f33fab1
--- /dev/null
+++ b/lib/libtabular/src/test.c
@@ -0,0 +1,154 @@
+#include "tabular.h"
+#include "allocator.h"
+
+#include <sys/ioctl.h>
+#include <err.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#define ARRLEN(x) (sizeof(x)/sizeof(*(x)))
+
+bool print_style(FILE *file, const struct tabular_cfg *cfg,
+ const struct tabular_row *row, const struct tabular_col *col);
+
+char *col_pos_str(const struct tabular_user *row, const struct tabular_user *col);
+bool col_pos_hidden(const struct tabular_user *user);
+
+char *col_name_str(const struct tabular_user *row, const struct tabular_user *col);
+bool col_name_hidden(const struct tabular_user *user);
+
+static const char **argv = NULL;
+
+static struct tabular_col columns[] = {
+ {
+ .name = "Pos",
+
+ .to_str = col_pos_str,
+ .is_hidden = NULL,
+
+ .minwidth = 3,
+ .maxwidth = 3,
+ .lpad = 0,
+ .rpad = 0,
+
+ .align = TABULAR_ALIGN_CENTER,
+
+ .strategy = TABULAR_TRUNC,
+ .essential = true,
+ },
+ {
+ .name = "Value",
+
+ .to_str = col_name_str,
+ .is_hidden = NULL,
+
+ .minwidth = 5,
+ .maxwidth = 80,
+ .lpad = 0,
+ .rpad = 0,
+
+ .align = TABULAR_ALIGN_LEFT,
+
+ .strategy = TABULAR_WRAP_WORDAWARE,
+ .essential = true,
+ },
+};
+
+static struct tabular_cfg cfg = {
+ .colors = 256,
+
+ .columns = columns,
+ .column_count = ARRLEN(columns),
+
+ .fit_rows = true,
+
+ .hsep = "│ ",
+ .vsep = "─",
+ .xsep = "┼─",
+
+ .outw = 0,
+ .outh = 0,
+
+ .lpad = 1,
+ .rpad = 1,
+
+ .print_style = print_style,
+
+ .skip_lines = 3,
+
+ .allocator = &stdlib_heap_allocator
+};
+
+bool
+print_style(FILE *file, const struct tabular_cfg *cfg,
+ const struct tabular_row *row, const struct tabular_col *col)
+{
+ if (cfg->colors == 256) {
+ if (!col) { /* separators */
+ fprintf(file, "\x1b[90m");
+ return true;
+ } else if (!row) { /* header */
+ fprintf(file, "\x1b[1m");
+ return true;
+ } else if (!strcmp(col->name, "Name")) {
+ fprintf(file, "\x1b[35m");
+ return true;
+ }
+ }
+
+ return false;
+}
+
+char *
+col_pos_str(const struct tabular_user *row, const struct tabular_user *col)
+{
+ char buf[16];
+
+ snprintf(buf, sizeof(buf), "%li", row->id);
+
+ return strdup(buf);
+}
+
+char *
+col_name_str(const struct tabular_user *row, const struct tabular_user *col)
+{
+ return strdup(argv[row->id]);
+}
+
+int
+main(int argc, const char **_argv)
+{
+ struct tabular_row *rows, **end;
+ struct tabular_stats stats;
+ struct winsize ws;
+ int i, rc;
+
+ argv = _argv;
+
+ rc = ioctl(1, TIOCGWINSZ, &ws);
+ if (!rc) {
+ cfg.outw = ws.ws_col;
+ cfg.outh = ws.ws_row;
+ } else {
+ cfg.outw = 80;
+ cfg.outh = 26;
+ }
+
+ rows = NULL;
+ end = &rows;
+ for (i = 0; i < argc; i++) {
+ *end = tabular_alloc_row(&cfg, &rc,
+ (struct tabular_user) { .id = (size_t) i });
+ if (!*end) errx(1, "tabular_append_row %i", rc);
+ end = &(*end)->next;
+ }
+
+ rc = tabular_format(stdout, &cfg, &stats, &rows);
+ if (rc) errx(1, "tabular_format %i", rc);
+
+ printf("\n%lu lines, %lu rows\n",
+ stats.lines_used, stats.rows_displayed);
+
+ tabular_free_rows(&cfg, rows);
+}
diff --git a/lib/libtabular/src/util.c b/lib/libtabular/src/util.c
new file mode 100644
index 0000000..c2baa24
--- /dev/null
+++ b/lib/libtabular/src/util.c
@@ -0,0 +1,146 @@
+#include "util.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+size_t
+u8strlen(const char *str)
+{
+ size_t i, len;
+
+ if (!str) return 0;
+
+ len = 0;
+ for (i = 0; str[i]; i++) {
+ if ((str[i] & 0xC0) != 0x80)
+ len += 1;
+ }
+
+ return len;
+}
+
+size_t
+u8strnlen(const char *str, size_t max)
+{
+ size_t i, len;
+
+ if (!str) return 0;
+
+ len = 0;
+ for (i = 0; str[i] && i < max; i++) {
+ if ((str[i] & 0xC0) != 0x80)
+ len += 1;
+ }
+
+ return len;
+}
+
+size_t
+u8rawlen(const char *str, size_t max)
+{
+ size_t i, len;
+
+ if (!str) return 0;
+
+ len = 0;
+ for (i = 0; str[i] && len < max; i++)
+ if ((str[i] & 0xC0) != 0x80) {
+ len += 1;
+ }
+
+ return i;
+}
+
+size_t
+print_pad(FILE *file, size_t len)
+{
+ fprintf(file, "%*.s", (int) len, "");
+
+ return len;
+}
+
+size_t
+print_hex(FILE *file, const void *bytes, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ fprintf(file, "%02X", *(uint8_t *)(bytes + i));
+
+ return len * 2;
+}
+
+size_t
+print_trunc(FILE *file, const char *str, size_t width)
+{
+ size_t len;
+
+ len = u8strlen(str);
+ if (len > width && width >= 2) {
+ fprintf(file, "%*.*s..", (int) width - 2, (int) width - 2, str);
+ } else if (len > width&& width < 2) {
+ fprintf(file, "%*.*s", (int) width, (int) width, "..");
+ } else {
+ fprintf(file, "%s", str);
+ }
+
+ return MIN(len, width);
+}
+
+size_t
+print_left(FILE *file, const char *str, size_t width, size_t padwidth)
+{
+ size_t rawlen, u8len;
+ ssize_t ret;
+
+ u8len = MIN(u8strlen(str), width);
+ rawlen = u8rawlen(str, u8len);
+
+ /* make up for invisible bytes */
+ padwidth += rawlen - u8len;
+
+ ret = fprintf(file, "%-*.*s", (int) padwidth, (int) rawlen, str);
+
+ return (size_t) MAX(0, ret);
+}
+
+size_t
+print_center(FILE *file, const char *str, size_t width, size_t padwidth)
+{
+ size_t u8len, rawlen;
+ size_t lpad, rpad;
+ ssize_t ret;
+
+ u8len = MIN(width, u8strlen(str));
+ rawlen = u8rawlen(str, u8len);
+ lpad = MAX(padwidth - u8len, 0) / 2;
+ rpad = MAX(padwidth - u8len - lpad, 0);
+
+ /* make up for invisible bytes */
+ rpad += rawlen - u8len;
+
+ ret = fprintf(file, "%*s%*.*s%*s", (int) lpad, "",
+ (int) rawlen, (int) rawlen, str, (int) rpad, "");
+
+ return (size_t) MAX(0, ret);
+}
+
+size_t
+print_right(FILE *file, const char *str, size_t width, size_t padwidth)
+{
+ size_t rawlen, u8len;
+ ssize_t ret;
+
+ u8len = MIN(width, u8strlen(str));
+ rawlen = u8rawlen(str, u8len);
+
+ /* make up for invisible bytes */
+ padwidth += rawlen - u8len;
+
+ ret = fprintf(file, "%*.*s", (int) padwidth, (int) rawlen, str);
+
+ return (size_t) MAX(0, ret);
+}
+
diff --git a/lib/libtabular/src/util.h b/lib/libtabular/src/util.h
new file mode 100644
index 0000000..a325b18
--- /dev/null
+++ b/lib/libtabular/src/util.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#define CEILDIV(a, b) (((a) / (b)) + !!((a) % (b)))
+
+size_t u8strlen(const char *str);
+size_t u8strnlen(const char *str, size_t max);
+size_t u8rawlen(const char *str, size_t max);
+
+size_t print_pad(FILE *file, size_t len);
+size_t print_hex(FILE *file, const void *in, size_t len);
+
+size_t print_trunc(FILE *file, const char *str, size_t width);
+size_t print_left(FILE *file, const char *str,
+ size_t width, size_t padwidth);
+size_t print_center(FILE *file, const char *str,
+ size_t width, size_t padwidth);
+size_t print_right(FILE *file, const char *str,
+ size_t width, size_t padwidth);
+
+