summaryrefslogtreecommitdiff
path: root/htab/table.ha
diff options
context:
space:
mode:
authorLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:11:21 +0200
committerLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:51:35 +0200
commitae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch)
tree5f462459ae4b47d22114eed717d1382d08cf4dfe /htab/table.ha
Initial commit (import old repo)HEADmain
Diffstat (limited to 'htab/table.ha')
-rw-r--r--htab/table.ha254
1 files changed, 254 insertions, 0 deletions
diff --git a/htab/table.ha b/htab/table.ha
new file mode 100644
index 0000000..b90b0aa
--- /dev/null
+++ b/htab/table.ha
@@ -0,0 +1,254 @@
+use fmt;
+use math;
+use rt;
+
+fn cap2nslots(cap: size) size = {
+ const nslots_ = cap << 1;
+ let nslots = 1z;
+ for (nslots < nslots_) {
+ nslots <<= 1;
+ };
+ return nslots;
+};
+
+fn nslots2cap(nslots: size) size = {
+ return nslots >> 1;
+};
+
+fn dbg(fmt: str, args: fmt::field...) void = {
+ fmt::errorfln(fmt, args...)!;
+};
+
+// temporary hack before inlining works.
+def DBG = false;
+
+export type table = struct {
+ table: *[*]u64,
+ nslots: size, // must be a power of 2.
+ nleft: size,
+};
+
+// The interface of user-specified equality comparison functions.
+export type eq_fn = fn(ctx: *opaque, elem: *opaque) bool;
+
+export const empty: [1]u64 = [0];
+
+// An empty hash table.
+def EMPTY = table {
+ table = &empty,
+ nslots = 1,
+ nleft = 0,
+};
+
+// Returns a new hash table preallocated with the specified capacity.
+//
+// If cap is 0, no memory is allocated, and the result is equivalent
+// to [[EMPTY]].
+export fn new(cap: size, sz: size) table = {
+ if (cap == 0) {
+ return EMPTY;
+ };
+
+ const nslots = cap2nslots(cap);
+
+ // TODO: need to check for overflow here
+ // TODO: should probably deal with alignment > 8 bytes
+ const allocsz = nslots * size(u64) + nslots * sz;
+ const ptr = rt::malloc(allocsz): *opaque;
+
+ let tab = table {
+ table = ptr: *[*]u64,
+ nslots = nslots,
+ ...
+ };
+ clear(&tab, sz);
+ return tab;
+};
+
+// Clears the table without deallocating it.
+//
+// Calling this function causes all previously obtained element pointers and
+// iterators belonging to the table to become invalid.
+export fn clear(tab: *table, sz: size) void = {
+ for (let i = 0z; i < tab.nslots; i += 1) {
+ tab.table[i] = 0;
+ };
+ tab.nleft = nslots2cap(tab.nslots);
+};
+
+// Clears and deallocates the table.
+//
+// The table is left in a valid, empty state.
+//
+// Calling this function causes all previously obtained element pointers and
+// iterators belonging to the table to become invalid.
+export fn finish(tab: *table) void = {
+ if (tab.nslots != 1) {
+ free(tab.table);
+ };
+ *tab = EMPTY;
+};
+
+// Returns the number of elements in the table.
+export fn count(tab: *table) size = {
+ return nslots2cap(tab.nslots) - tab.nleft;
+};
+
+fn at(tab: *table, i: size, sz: size) *opaque = {
+ const elems: *opaque = &tab.table[tab.nslots];
+ return (elems: uintptr + i * sz): *opaque;
+};
+
+// Resizes the table to the specified capacity.
+//
+// The capacity specifies the maximum number of elements storable without
+// growing the table. The resulting memory allocation will be larger.
+//
+// If the number of elements in the table exceeds the capacity,
+// the program aborts.
+//
+// Calling this function causes all previously obtained element pointers and
+// iterators belonging to the table to become invalid.
+export fn resize(tab: *table, newcap: size, sz: size) void = {
+ if (cap2nslots(newcap) == tab.nslots) {
+ return;
+ };
+ assert(count(tab) <= newcap,
+ "table won't fit into requested capacity");
+ if (DBG) dbg("resize {} {{", newcap);
+ let newtab = new(newcap, sz);
+ for (let i = 0z; i < tab.nslots; i += 1) {
+ if (tab.table[i] == 0) {
+ continue;
+ };
+ const entry = add(&newtab, tab.table[i], sz);
+ rt::memcpy(entry, at(tab, i, sz), sz);
+ };
+ finish(tab);
+ *tab = newtab;
+ if (DBG) dbg("}} resize {}", newcap);
+};
+
+// Finds an element with the specified hash in the table.
+//
+// If no element is found, null is returned.
+//
+// hash must not equal 0.
+//
+// If eq is non-null, it is called on each element matching the hash until it
+// returns true. If it is null, an arbitrary matching element is returned.
+//
+// ctx specifies the value of the ctx argument passed to eq.
+export fn get(tab: *table, hash: u64, eq: *eq_fn, ctx: *opaque, sz: size)
+ nullable *opaque = {
+ if (DBG) dbg("get {}", hash);
+ let i = hash: size & tab.nslots - 1;
+ for (let inc = 0z; true; inc = 1) {
+ i = i + inc & tab.nslots - 1;
+ if (DBG) dbg("[{}] -> {}", i, tab.table[i]);
+ if (tab.table[i] == 0) {
+ break;
+ };
+ if (tab.table[i] != hash) {
+ continue;
+ };
+ const elem = at(tab, i, sz);
+ if (!eq(ctx, elem)) {
+ continue;
+ };
+ return elem;
+ };
+ return null;
+};
+
+// Adds an element with the specified hash to the table and returns a pointer
+// to it.
+//
+// The element is left uninitialized.
+//
+// hash must not equal 0.
+//
+// Calling this function causes all previously obtained element pointers and
+// iterators belonging to the table to become invalid.
+//
+// Unless the table is known not to already contain an identical element, users
+// should first use [[get]] to check for that possibility.
+export fn add(tab: *table, hash: u64, sz: size) *opaque = {
+ if (DBG) dbg("add {}", hash);
+
+ if (tab.nleft == 0) {
+ resize(tab, nslots2cap(tab.nslots) + 1, sz);
+ };
+ tab.nleft -= 1;
+
+ let i = hash: size & tab.nslots - 1;
+ for (let inc = 0z; true; inc = 1) {
+ i = i + inc & tab.nslots - 1;
+ if (DBG) dbg("[{}] -> {}", i, tab.table[i]);
+ if (tab.table[i] != 0) {
+ continue;
+ };
+ if (DBG) dbg("[{}] <- {}", i, hash);
+ tab.table[i] = hash;
+ return at(tab, i, sz);
+ };
+};
+
+// Removes the specified element from the table.
+//
+// Calling this function causes all previously obtained element pointers and
+// iterators belonging to the table to become invalid.
+export fn del(tab: *table, elem: *opaque, sz: size) void = {
+ tab.nleft += 1;
+
+ let i = (elem: uintptr - at(tab, 0, sz): uintptr): size / sz;
+ if (DBG) dbg("del {}", i);
+ if (DBG) dbg("[{}] <- {}", i, 0);
+ tab.table[i] = 0;
+
+ for (let j = i; true) {
+ j = j + 1 & tab.nslots - 1;
+ if (DBG) dbg("[{}] -> {}", j, tab.table[j]);
+ if (tab.table[j] == 0) {
+ break;
+ };
+ const k = tab.table[j]: size & tab.nslots - 1;
+ if (if (i < j) i < k && k <= j else i < k || k <= j) {
+ continue;
+ };
+ if (DBG) dbg("[{}] <- {}", i, tab.table[j]);
+ tab.table[i] = tab.table[j];
+ rt::memcpy(at(tab, i, sz), at(tab, j, sz), sz);
+ if (DBG) dbg("[{}] <- {}", j, 0);
+ tab.table[j] = 0;
+ i = j;
+ };
+};
+
+export type iterator = struct {
+ table: *table,
+ i: size,
+};
+
+// Returns a new iterator for the table.
+//
+// The order of iteration is unspecified.
+export fn iter(tab: *table) iterator = {
+ return iterator {
+ table = tab,
+ i = 0,
+ };
+};
+
+// Returns a pointer to the next element from the iterator, or null if iteration
+// has completed.
+export fn next(it: *iterator, sz: size) nullable *opaque = {
+ for (it.i < it.table.nslots) {
+ const i = it.i;
+ it.i += 1;
+ if (it.table.table[i] != 0) {
+ return at(it.table, i, sz);
+ };
+ };
+ return null;
+};