diff options
| author | Lassi Pulkkinen <lassi@pulk.fi> | 2024-10-31 03:11:21 +0200 | 
|---|---|---|
| committer | Lassi Pulkkinen <lassi@pulk.fi> | 2024-10-31 03:51:35 +0200 | 
| commit | ae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch) | |
| tree | 5f462459ae4b47d22114eed717d1382d08cf4dfe /htab/table.ha | |
Diffstat (limited to 'htab/table.ha')
| -rw-r--r-- | htab/table.ha | 254 | 
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; +};  | 
