Lindenii Project Forge
Login

hare-ds

Data structures for Hare
Commit info
ID
e0631c30ebaa884b773c08d20c1cfe000edbb3b0
Author
Runxi Yu <me@runxiyu.org>
Author date
Wed, 17 Sep 2025 00:37:10 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Wed, 17 Sep 2025 00:37:10 +0800
Actions
Remove the map_ prefix
btree: b-tree map
map_btree: key-value map implemented with a b-tree
// SPDX-License-Identifier: MPL-2.0

use bytes;
use sort;

// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	const r = delete_rec(m, m.root, key);
	if (len(m.root.keys) == 0 && !m.root.leaf) {
		m.root = m.root.children[0];
	};
	return r;
};

// SPDX-License-Identifier: MPL-2.0

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	node_finish(m.root);
	free(m);
};

fn node_finish(n: *node) void = {
	if (!n.leaf) {
		for (let i = 0z; i < len(n.children); i += 1) {
			node_finish(n.children[i]);
		};
		free(n.children);
	};
	free(n.keys);
	free(n.vals);
	free(n);
};
// SPDX-License-Identifier: MPL-2.0

use bytes;
use sort;

// Gets an item from a [[map]] by key, returning void if not found.
export fn get(m: *map, key: []u8) (*opaque | void) = {
	let x = m.root;
	for (true) {
		let i = sort::lbisect((x.keys: []const opaque), size([]u8), (&key: const *opaque), &cmp_u8slice);
		if (i < len(x.keys) && bytes::equal(x.keys[i], key)) {
			return x.vals[i];
		};
		if (x.leaf) {
			return;
		};
		x = x.children[i];
	};
};
// SPDX-License-Identifier: MPL-2.0

use bytes;
use sort;

fn keycmp(a: []u8, b: []u8) int = {
	let n = if (len(a) < len(b)) len(a) else len(b);
	for (let i = 0z; i < n; i += 1) {
		if (a[i] < b[i]) return -1;
		if (a[i] > b[i]) return 1;
	};
	if (len(a) < len(b)) return -1;
	if (len(a) > len(b)) return 1;
	return 0;
};

fn cmp_u8slice(a: const *opaque, b: const *opaque) int = {
	let sa = *(a: *[]u8);
	let sb = *(b: *[]u8);
	return keycmp(sa, sb);
};

fn node_new(t: size, leaf: bool) (*node | nomem) = {
	let capk = 2 * t - 1;
	let capc = if (leaf) 0z else 2z * t;

	let empty_keys: [][]u8 = [];
	let keys = alloc(empty_keys, capk)?;

	let empty_vals: []*opaque = [];
	let vals = alloc(empty_vals, capk)?;

	let children: []*node = if (leaf) {
		yield [];
	} else {
		let empty_children: []*node = [];
		yield alloc(empty_children, capc)?;
	};

	let nd = alloc(node {
		leaf = leaf,
		keys = keys,
		vals = vals,
		children = children,
	})?;
	return nd;
};

fn split_child(m: *map, x: *node, i: size) (void | nomem) = {
	const t = m.t;
	let y = x.children[i];
	let z = node_new(t, y.leaf)?;

	let medk = y.keys[t - 1];
	let medv = y.vals[t - 1];

	append(z.keys, y.keys[t..]...)?;
	append(z.vals, y.vals[t..]...)?;
	if (!y.leaf) {
		append(z.children, y.children[t..]...)?;
	};

	y.keys = y.keys[..t - 1];
	y.vals = y.vals[..t - 1];
	if (!y.leaf) {
		y.children = y.children[..t];
	};

	insert(x.keys[i], medk)?;
	insert(x.vals[i], medv)?;
	insert(x.children[i + 1], z)?;
};

fn insert_nonfull(m: *map, x: *node, key: []u8, val: *opaque) (void | nomem) = {
	let i = sort::lbisect((x.keys: []const opaque), size([]u8),
		(&key: const *opaque), &cmp_u8slice);

	if (i < len(x.keys) && bytes::equal(x.keys[i], key)) {
		x.vals[i] = val;
		return;
	};

	if (x.leaf) {
		insert(x.keys[i], key)?;
		insert(x.vals[i], val)?;
		return;
	};

	if (len(x.children[i].keys) == 2 * m.t - 1) {
		split_child(m, x, i)?;
		if (cmp_u8slice((&key: const *opaque),
				(&x.keys[i]: const *opaque)) > 0) {
			insert_nonfull(m, x.children[i + 1], key, val)?;
			return;
		};
	};
	insert_nonfull(m, x.children[i], key, val)?;
};

fn merge_children(m: *map, x: *node, i: size) void = {
	let left = x.children[i];
	let right = x.children[i + 1];

	insert(left.keys[len(left.keys)], x.keys[i])!;
	insert(left.vals[len(left.vals)], x.vals[i])!;

	append(left.keys, right.keys...)!;
	append(left.vals, right.vals...)!;
	if (!left.leaf) {
		append(left.children, right.children...)!;
	};

	delete(x.keys[i]);
	delete(x.vals[i]);
	delete(x.children[i + 1]);
};

fn ensure_child_has_space(m: *map, x: *node, i: size) void = {
	const t = m.t;
	let c = x.children[i];

	if (len(c.keys) >= t) return;

	if (i > 0 && len(x.children[i - 1].keys) >= t) {
		let ls = x.children[i - 1];

		insert(c.keys[0], x.keys[i - 1])!;
		insert(c.vals[0], x.vals[i - 1])!;

		if (!c.leaf) {
			let moved = ls.children[len(ls.children) - 1];
			insert(c.children[0], moved)!;
			delete(ls.children[len(ls.children) - 1]);
		};

		x.keys[i - 1] = ls.keys[len(ls.keys) - 1];
		x.vals[i - 1] = ls.vals[len(ls.vals) - 1];
		delete(ls.keys[len(ls.keys) - 1]);
		delete(ls.vals[len(ls.vals) - 1]);
		return;
	};

	if (i + 1 < len(x.children) && len(x.children[i + 1].keys) >= t) {
		let rs = x.children[i + 1];

		insert(c.keys[len(c.keys)], x.keys[i])!;
		insert(c.vals[len(c.vals)], x.vals[i])!;

		if (!c.leaf) {
			let moved = rs.children[0];
			insert(c.children[len(c.children)], moved)!;
			delete(rs.children[0]);
		};

		x.keys[i] = rs.keys[0];
		x.vals[i] = rs.vals[0];
		delete(rs.keys[0]);
		delete(rs.vals[0]);
		return;
	};

	if (i + 1 < len(x.children)) {
		merge_children(m, x, i);
	} else {
		merge_children(m, x, i - 1);
	};
};

fn pop_max(m: *map, x: *node) ([]u8, *opaque) = {
	let cur = x;
	for (!cur.leaf) {
		let last = len(cur.children) - 1;
		ensure_child_has_space(m, cur, last);
		cur = cur.children[last];
	};
	let k = cur.keys[len(cur.keys) - 1];
	let v = cur.vals[len(cur.vals) - 1];
	delete(cur.keys[len(cur.keys) - 1]);
	delete(cur.vals[len(cur.vals) - 1]);
	return (k, v);
};

fn pop_min(m: *map, x: *node) ([]u8, *opaque) = {
	let cur = x;
	for (!cur.leaf) {
		ensure_child_has_space(m, cur, 0);
		cur = cur.children[0];
	};
	let k = cur.keys[0];
	let v = cur.vals[0];
	delete(cur.keys[0]);
	delete(cur.vals[0]);
	return (k, v);
};

fn delete_rec(m: *map, x: *node, key: []u8) (*opaque | void) = {
	let i = sort::lbisect((x.keys: []const opaque), size([]u8),
		(&key: const *opaque), &cmp_u8slice);

	if (i < len(x.keys) && bytes::equal(x.keys[i], key)) {
		if (x.leaf) {
			let ret = x.vals[i];
			delete(x.keys[i]);
			delete(x.vals[i]);
			return ret;
		};

		const t = m.t;
		let y = x.children[i];
		let z = x.children[i + 1];

		if (len(y.keys) >= t) {
			let (pk, pv) = pop_max(m, y);
			let ret = x.vals[i];
			x.keys[i] = pk;
			x.vals[i] = pv;
			return ret;
		} else if (len(z.keys) >= t) {
			let (sk, sv) = pop_min(m, z);
			let ret = x.vals[i];
			x.keys[i] = sk;
			x.vals[i] = sv;
			return ret;
		} else {
			merge_children(m, x, i);
			return delete_rec(m, y, key);
		};
	};

	if (x.leaf) {
		return;
	};

	ensure_child_has_space(m, x, i);
	return delete_rec(m, x.children[i], key);
};
// SPDX-License-Identifier: MPL-2.0

use ds::map;

// B-tree-based map from []u8 to *opaque.
//
// You are advised to create these with [[new]].
export type map = struct {
	vt: map::map,
	// Min degree
	t: size,
	root: *node,
};

const _vt: map::vtable = map::vtable {
	getter   = &vt_get,
	setter   = &vt_set,
	deleter  = &vt_del,
	finisher = &vt_finish,
};

fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key);
fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v);
fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key);
fn vt_finish(m: *map::map) void = finish(m: *map);
// SPDX-License-Identifier: MPL-2.0

use errors;

// Creates a new [[map]] with minimum degree t.
//
// t must be greater than or equal to 2.
export fn new(t: size) (*map | errors::invalid | nomem) = {
	if (t < 2) {
		return errors::invalid;
	};
	let r = node_new(t, true)?;
	let m = alloc(map {
		vt = &_vt,
		t = t,
		root = r,
	})?;
	return m;
};
export type node = struct {
	leaf: bool,
	keys: [][]u8,
	vals: []*opaque,
	children: []*node,
};

// SPDX-License-Identifier: MPL-2.0

use bytes;
use sort;

// Sets an item in a [[map]], replacing any existing item with the same key.
export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	if (len(m.root.keys) == 2 * m.t - 1) {
		let s = node_new(m.t, false)?;
		append(s.children, m.root)?;
		m.root = s;
		split_child(m, s, 0)?;
	};
	insert_nonfull(m, m.root, key, value)?;
};
map_fnv: key-value map implemented as a Fowler-Noll-Vo hashmap
fnv: Fowler-Noll-Vo hashmap

This module provides a simple implementation of a hashmap using the
Fowler-Noll-Vo (FNV) hashing algorithm.

FNV is not collision-resistant, so it should only be used for trusted keys
(i.e., not user input that could be deliberately chosen to cause collisions).
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Deletes an item from a [[map]].
export fn del(m: *map, key: []u8) (*opaque | void) = {
	return map::del(m.inner, key);
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	map::finish(m.inner);
	free(m);
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Gets an item from a [[map]] by key, returning void if not found.
export fn get(m: *map, key: []u8) (*opaque | void) = {
	return map::get(m.inner, key);
};
use hash;
use hash::fnv;

fn hash64(_params: nullable *opaque, key: []u8) size = {
	let h = fnv::fnv64a();
	hash::write(&h, key);
	return fnv::sum64(&h): size;
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// A simple hash map from byte strings to opaque pointers, using FNV for
// hashing and fallback maps for collision resolution.
//
// You are advised to create these with [[new]].
export type map = struct {
	vt: map::map,
	inner: *map::map,
};

const _vt: map::vtable = map::vtable {
	getter   = &vt_get,
	setter   = &vt_set,
	deleter  = &vt_del,
	finisher = &vt_finish,
};

fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key);
fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v);
fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key);
fn vt_finish(m: *map::map) void = finish(m: *map);
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use errors;
use ds::map;
use ds::map::map_hashmap;
use ds::map::hashmap;

// Creates a new [[map]] with the given number of buckets.
// make_fallback is a function that creates per-bucket fallback maps.
export fn new(
	make_fallback: *fn() (*map::map | nomem),
	n: size,
) (*map | errors::invalid | nomem) = {
	let inner = match (map_hashmap::new(make_fallback, n, &hash64, null)) {
	case let hm: *map_hashmap::map =>
	let inner = match (hashmap::new(make_fallback, n, &hash64, null)) {
	case let hm: *hashmap::map =>
		yield (hm: *map::map);
	case errors::invalid =>
		return errors::invalid;
	case nomem =>
		return nomem;
	};

	let m = match (alloc(map {
		vt = &_vt,
		inner = inner,
	})) {
	case let p: *map => yield p;
	case nomem =>
		map::finish(inner);
		return nomem;
	};
	return m;
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Sets an item in a [[map]], replacing any existing item with the same key.
export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	return map::set(m.inner, key, value);
};
map_hashmap: key-value map implemented as a generic hashmap
hashmap: generic hashmap

This module provides a simple implementation of a hashmap from []u8 to *opaque.
The hash algorithm and fallback collision resolution strategy are both
configurable.
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use bytes;
use hash;
use hash::siphash;
use ds::map;

// Deletes an item from a [[map]].
export fn del(m: *map, key: []u8) (*opaque | void) = {
	let b = m.buckets[m.hash64(m.hash_params, key) % m.n];
	return map::del(b, key);
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	for (let i = 0z; i < m.n; i += 1) {
		map::finish(m.buckets[i]);
	};
	free(m.buckets);
	free(m);
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use bytes;
use hash;
use hash::siphash;
use ds::map;

// Gets an item from a [[map]] by key, returning void if not found.
export fn get(m: *map, key: []u8) (*opaque | void) = {
	let b = m.buckets[m.hash64(m.hash_params, key) % m.n];
	return map::get(b, key);
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// A simple hash map from []u8 to *opaque pointers, using the user-provided hash
// and collision resolution function.
//
// You are advised to create these with [[new]].
export type map = struct {
	vt: map::map,
	n: size,
	buckets: []*map::map,
	hash64: *fn(hash_params: nullable *opaque, key: []u8) size,
	hash_params: nullable *opaque,
};

const _vt: map::vtable = map::vtable {
	getter   = &vt_get,
	setter   = &vt_set,
	deleter  = &vt_del,
	finisher = &vt_finish,
};

fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key);
fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v);
fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key);
fn vt_finish(m: *map::map) void = finish(m: *map);
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use bytes;
use errors;
use ds::map;

// Creates a new [[map]] with a function that creates fallback maps, the number
// of buckets, and the hash function and parameters.
export fn new(
	make_fallback: *fn() (*map::map | nomem),
	n: size,
	hash64: *fn(hash_params: nullable *opaque, key: []u8) size,
	hash_params: nullable *opaque,
) (*map | errors::invalid | nomem) = {
	if (n == 0) {
		return errors::invalid;
	};

	let buckets: []*map::map = [];
	for (let i = 0z; i < n; i += 1) {
		let fb = match (make_fallback()) {
		case let p: *map::map => yield p;
		case nomem =>
			for (let j = 0z; j < len(buckets); j += 1) {
				map::finish(buckets[j]);
			};
			return nomem;
		};
		match (append(buckets, fb)) {
		case void => yield;
		case nomem =>
			for (let j = 0z; j < len(buckets); j += 1) {
				map::finish(buckets[j]);
			};
			return nomem;
		};
	};

	let m = match (alloc(map {
		vt = &_vt,
		n = n,
		buckets = buckets,
		hash64 = hash64,
		hash_params = hash_params,
	})) {
	case let pm: *map => yield pm;
	case nomem =>
		for (let j = 0z; j < len(buckets); j += 1) {
			map::finish(buckets[j]);
		};
		free(buckets);
		return nomem;
	};
	return m;
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use bytes;
use hash;
use hash::siphash;
use ds::map;

// Sets an item in a [[map]], replacing any existing item with the same key.
export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	let b = m.buckets[m.hash64(m.hash_params, key) % m.n];
	return map::set(b, key, value);
};
map_rbtree: key-value map implemented as a red–black tree
// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	let z = find_node(m, key);
	match (z) {
	case null => return;
	case let nodez: *node =>
		let ret = nodez.val;

		let y = nodez;
		let y_orig = y.color;

		let x: nullable *node = null;
		let p_for_fix: nullable *node = null;

		if (nodez.left == null) {
			x = nodez.right;
			p_for_fix = nodez.parent;
			transplant(m, nodez, nodez.right);
		} else if (nodez.right == null) {
			x = nodez.left;
			p_for_fix = nodez.parent;
			transplant(m, nodez, nodez.left);
		} else {
			let r = match (nodez.right) {
			case let rr: *node => yield rr;
			case null => abort("rb invariant violated: del: right is null");
			};
			let s = subtree_min(r);
			y = s;
			let yor = y.color;
			y_orig = yor;

			x = y.right;
			if (y.parent == (nodez: nullable *node)) {
				p_for_fix = y;
				set_parent(x, y);
			} else {
				p_for_fix = y.parent;
				transplant(m, y, y.right);
				y.right = nodez.right;
				set_parent(y.right, y);
			};

			transplant(m, nodez, y);
			y.left = nodez.left;
			set_parent(y.left, y);
			y.color = nodez.color;
		};

		free(nodez);

		if (y_orig == color::BLACK) {
			delete_fixup(m, x, p_for_fix);
		};

		return ret;
	};
};
fn free_subtree(n: nullable *node) void = {
	match (n) {
	case null => return;
	case let p: *node =>
		free_subtree(p.left);
		free_subtree(p.right);
		free(p);
	};
};

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	free_subtree(m.root);
	free(m);
};
// SPDX-License-Identifier: MPL-2.0

use bytes;

// Gets an item from a [[map]] by key, returning void if not found.
export fn get(m: *map, key: []u8) (*opaque | void) = {
	let n = find_node(m, key);
	match (n) {
	case null => return;
	case let p: *node => return p.val;
	};
};
use bytes;

fn keycmp(a: []u8, b: []u8) int = {
	let n = if (len(a) < len(b)) len(a) else len(b);
	for (let i = 0z; i < n; i += 1) {
		if (a[i] < b[i]) return -1;
		if (a[i] > b[i]) return 1;
	};
	if (len(a) < len(b)) return -1;
	if (len(a) > len(b)) return 1;
	return 0;
};

fn is_red(p: nullable *node) bool = {
	match (p) {
	case null => return false;
	case let n: *node => return n.color == color::RED;
	};
};

fn is_black(p: nullable *node) bool = !is_red(p);

fn set_color(p: nullable *node, c: color) void = {
	match (p) {
	case null => return;
	case let n: *node => n.color = c;
	};
};

fn set_parent(ch: nullable *node, pa: nullable *node) void = {
	match (ch) {
	case null => return;
	case let n: *node => n.parent = pa;
	};
};

fn subtree_min(n: *node) *node = {
	let cur = n;
	for (true) {
		match (cur.left) {
		case null => return cur;
		case let l: *node => cur = l;
		};
	};
};

fn transplant(m: *map, u: *node, v: nullable *node) void = {
	match (u.parent) {
	case null =>
		m.root = v;
	case let p: *node =>
		if (p.left == (u: nullable *node)) {
			p.left = v;
		} else {
			p.right = v;
		};
	};
	set_parent(v, u.parent);
};

fn rotate_left(m: *map, x: *node) void = {
	let y = match (x.right) {
	case let r: *node => yield r;
	case null => abort("rb invariant violated: rotate_left with null right");
	};

	x.right = y.left;
	set_parent(x.right, x);

	y.parent = x.parent;
	match (x.parent) {
	case null =>
		m.root = y;
	case let p: *node =>
		if (p.left == (x: nullable *node)) {
			p.left = y;
		} else {
			p.right = y;
		};
	};

	y.left = x;
	x.parent = y;
};

fn rotate_right(m: *map, x: *node) void = {
	let y = match (x.left) {
	case let l: *node => yield l;
	case null => abort("rb invariant violated: rotate_right with null left");
	};

	x.left = y.right;
	set_parent(x.left, x);

	y.parent = x.parent;
	match (x.parent) {
	case null =>
		m.root = y;
	case let p: *node =>
		if (p.left == (x: nullable *node)) {
			p.left = y;
		} else {
			p.right = y;
		};
	};

	y.right = x;
	x.parent = y;
};

fn insert_fixup(m: *map, z: *node) void = {
	let cur = z;
	for (true) {
		let p = match (cur.parent) {
		case null => break;
		case let pp: *node => yield pp;
		};
		if (p.color == color::BLACK) break;

		let gp = match (p.parent) {
		case null => break;
		case let g: *node => yield g;
		};

		let uncle: nullable *node = if (gp.left == (p: nullable *node))
			gp.right else gp.left;

		if (is_red(uncle)) {
			set_color(p, color::BLACK);
			set_color(uncle, color::BLACK);
			set_color(gp, color::RED);
			cur = gp;
			continue;
		};

		if (gp.left == (p: nullable *node)) {
			if (p.right == (cur: nullable *node)) {
				rotate_left(m, p);
				cur = p;
			};
			let p2 = match (cur.parent) {
			case null => break;
			case let pp2: *node => yield pp2;
			};
			let g2 = match (p2.parent) {
			case null => break;
			case let gg2: *node => yield gg2;
			};
			set_color(p2, color::BLACK);
			set_color(g2, color::RED);
			rotate_right(m, g2);
		} else {
			if (p.left == (cur: nullable *node)) {
				rotate_right(m, p);
				cur = p;
			};
			let p2 = match (cur.parent) {
			case null => break;
			case let pp2: *node => yield pp2;
			};
			let g2 = match (p2.parent) {
			case null => break;
			case let gg2: *node => yield gg2;
			};
			set_color(p2, color::BLACK);
			set_color(g2, color::RED);
			rotate_left(m, g2);
		};
	};
	match (m.root) {
	case null => void;
	case let r: *node => r.color = color::BLACK;
	};
};

fn find_node(m: *map, key: []u8) nullable *node = {
	let cur = m.root;
	for (true) {
		match (cur) {
		case null => return null;
		case let n: *node =>
			let c = keycmp(key, n.key);
			if (c == 0) return n;
			cur = if (c < 0) n.left else n.right;
		};
	};
};

fn delete_fixup(m: *map, x0: nullable *node, p0: nullable *node) void = {
	let x = x0;
	let p = p0;

	for (x != m.root && is_black(x)) {
		let pp = match (p) {
		case null => break;
		case let q: *node => yield q;
		};

		if (pp.left == x) {
			let mutw = pp.right;
			let w = match (mutw) {
			case null => break;
			case let w_: *node => yield w_;
			};
			if (is_red(w)) {
				set_color(w, color::BLACK);
				set_color(pp, color::RED);
				rotate_left(m, pp);
				mutw = pp.right;
			};
			let wr = match (mutw) {
			case null => yield null;
			case let w2: *node => yield w2.right;
			};
			let wl = match (mutw) {
			case null => yield null;
			case let w2: *node => yield w2.left;
			};
			if (is_black(wl) && is_black(wr)) {
				set_color(mutw, color::RED);
				x = pp;
				p = pp.parent;
			} else {
				if (is_black(wr)) {
					set_color(wl, color::BLACK);
					set_color(mutw, color::RED);
					match (mutw) {
					case null => void;
					case let ww: *node => rotate_right(m, ww);
					};
					mutw = pp.right;
				};
				match (mutw) {
				case null => void;
				case let w3: *node =>
					w3.color = pp.color;
					set_color(pp, color::BLACK);
					set_color(w3.right, color::BLACK);
					rotate_left(m, pp);
				};
				x = m.root;
				p = null;
			};
		} else {
			let mutw = pp.left;
			let w = match (mutw) {
			case null => break;
			case let w_: *node => yield w_;
			};
			if (is_red(w)) {
				set_color(w, color::BLACK);
				set_color(pp, color::RED);
				rotate_right(m, pp);
				mutw = pp.left;
			};
			let wl = match (mutw) {
			case null => yield null;
			case let w2: *node => yield w2.left;
			};
			let wr = match (mutw) {
			case null => yield null;
			case let w2: *node => yield w2.right;
			};
			if (is_black(wl) && is_black(wr)) {
				set_color(mutw, color::RED);
				x = pp;
				p = pp.parent;
			} else {
				if (is_black(wl)) {
					set_color(wr, color::BLACK);
					set_color(mutw, color::RED);
					match (mutw) {
					case null => void;
					case let ww: *node => rotate_left(m, ww);
					};
					mutw = pp.left;
				};
				match (mutw) {
				case null => void;
				case let w3: *node =>
					w3.color = pp.color;
					set_color(pp, color::BLACK);
					set_color(w3.left, color::BLACK);
					rotate_right(m, pp);
				};
				x = m.root;
				p = null;
			};
		};
	};
	set_color(x, color::BLACK);
};
use ds::map;

// Red–black tree-based map from []u8 to *opaque.
//
// You are advised to create these with [[new]].
export type map = struct {
	vt: map::map,
	root: nullable *node,
};

const _vt: map::vtable = map::vtable {
	getter   = &vt_get,
	setter   = &vt_set,
	deleter  = &vt_del,
	finisher = &vt_finish,
};

fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key);
fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v);
fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key);
fn vt_finish(m: *map::map) void = finish(m: *map);
// Creates a new [[map]].
export fn new() (*map | nomem) = {
	let m = alloc(map {
		vt = &_vt,
		root = null,
	})?;
	return m;
};
// SPDX-License-Identifier: MPL-2.0

export type color = enum u8 {
	RED = 0,
	BLACK = 1,
};

export type node = struct {
	color: color,
	key: []u8,
	val: *opaque,
	left: nullable *node,
	right: nullable *node,
	parent: nullable *node,
};
use bytes;

export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	match (find_node(m, key)) {
	case let ex: *node =>
		ex.val = value;
		return;
	case null => void;
	};

	let z = alloc(node {
		color = color::RED,
		key = key,
		val = value,
		left = null,
		right = null,
		parent = null,
	})?;

	let y: nullable *node = null;
	let x = m.root;

	for (true) {
		match (x) {
		case null => break;
		case let xn: *node =>
			y = xn;
			if (keycmp(z.key, xn.key) < 0) {
				x = xn.left;
			} else {
				x = xn.right;
			};
		};
	};

	z.parent = y;
	match (y) {
	case null =>
		m.root = z;
	case let yn: *node =>
		if (keycmp(z.key, yn.key) < 0) {
			yn.left = z;
		} else {
			yn.right = z;
		};
	};

	insert_fixup(m, z);
};
map_siphash: key-value map implemented as a SipHash hashmap
siphash: SipHash hashmap

This module provides a simple implementation of a hashmap using the SipHash
hashing algorithm for collision-resistant mapping. It is designed for
situations where keys may be untrusted.
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Deletes an item from a [[map]].
export fn del(m: *map, key: []u8) (*opaque | void) = {
	return map::del(m.inner, key);
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	map::finish(m.inner);
	free(m.key);
	free(m);
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Gets an item from a [[map]] by key, returning void if not found.
export fn get(m: *map, key: []u8) (*opaque | void) = {
	return map::get(m.inner, key);
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use hash;
use hash::siphash;

fn hash64(params: nullable *opaque, key: []u8) size = {
	let keyptr = match (params) {
	case null =>
		abort("map_siphash: missing key");
		abort("ds::map::siphash: missing key");
	case let p: *opaque =>
		yield (p: *[16]u8);
	};

	let h = siphash::siphash(2, 4, keyptr);
	defer hash::close(&h);
	hash::write(&h, key);
	return siphash::sum(&h): size;
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// A simple hash map from byte strings to opaque pointers, using SipHash for
// hashing and fallback maps for collision resolution.
//
// You are advised to create these with [[new]].
export type map = struct {
	vt: map::map,
	inner: *map::map,
	key: *[16]u8,
};

const _vt: map::vtable = map::vtable {
	getter   = &vt_get,
	setter   = &vt_set,
	deleter  = &vt_del,
	finisher = &vt_finish,
};

fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key);
fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v);
fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key);
fn vt_finish(m: *map::map) void = finish(m: *map);
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use errors;
use ds::map;
use ds::map::map_hashmap;
use ds::map::hashmap;

// Creates a new [[map]] with a function that creates fallback maps, the number
// of buckets, and the SipHash key.
export fn new(
	make_fallback: *fn() (*map::map | nomem),
	n: size,
	siphash_key: [16]u8,
) (*map | errors::invalid | nomem) = {
	let keybox = match (alloc(siphash_key)) {
	case let kp: *[16]u8 => yield kp;
	case nomem => return nomem;
	};

	let inner = match (map_hashmap::new(
	let inner = match (hashmap::new(
		make_fallback, n, &hash64, (keybox: *opaque),
	)) {
	case let hm: *map_hashmap::map =>
	case let hm: *hashmap::map =>
		yield (hm: *map::map);
	case errors::invalid =>
		free(keybox);
		return errors::invalid;
	case nomem =>
		free(keybox);
		return nomem;
	};

	let m = match (alloc(map {
		vt = &_vt,
		inner = inner,
		key = keybox,
	})) {
	case let p: *map => yield p;
	case nomem =>
		map::finish(inner);
		free(keybox);
		return nomem;
	};
	return m;
};
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org>
// SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org>

use ds::map;

// Sets an item in a [[map]], replacing any existing item with the same key.
export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	return map::set(m.inner, key, value);
};
map_slice_basic: trivial key-value map backed by a single slice
// SPDX-License-Identifier: MPL-2.0

use bytes;

// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	for (let i = 0z; i < len(m.items); i += 1) {
		if (bytes::equal(m.items[i].0, key)) {
			let v = m.items[i].1;
			delete(m.items[i]);
			return v;
		};
	};
};
// SPDX-License-Identifier: MPL-2.0

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	free(m.items);
	free(m);
};
// SPDX-License-Identifier: MPL-2.0

use bytes;

// Gets an item from a [[map]] by key, returning void if not found.
export fn get(m: *map, key: []u8) (*opaque | void) = {
	for (let i = 0z; i < len(m.items); i += 1) {
		if (bytes::equal(m.items[i].0, key)) {
			return m.items[i].1;
		};
	};
};
// SPDX-License-Identifier: MPL-2.0

use ds::map;

// Slice-backed map from []u8 to *opaque using linear scanning.
//
// You are advised to create these with [[new]].
export type map = struct {
	vt: map::map,
	items: []([]u8, *opaque),
};

const _vt: map::vtable = map::vtable {
	getter   = &vt_get,
	setter   = &vt_set,
	deleter  = &vt_del,
	finisher = &vt_finish,
};

fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key);
fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v);
fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key);
fn vt_finish(m: *map::map) void = finish(m: *map);
// SPDX-License-Identifier: MPL-2.0

// Creates a new [[map]].
export fn new() (*map | nomem) = {
	let m = alloc(map {
		vt = &_vt,
		items = [],
	})?;
	return m;
};
// SPDX-License-Identifier: MPL-2.0

use bytes;

// Sets an item in a [[map]], replacing any existing item with the same key.
export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	for (let i = 0z; i < len(m.items); i += 1) {
		if (bytes::equal(m.items[i].0, key)) {
			m.items[i].1 = value;
			return;
		};
	};
	append(m.items, (key, value))?;
};
map_slice_sorted: sorted slice key-value store with binary search
use sort;

// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	let dummy = 0;
	let probe = (key, (&dummy: *opaque));
	match (sort::search((m.items: []const opaque),
		size(([]u8, *opaque)),
		(&probe: const *opaque), &cmp_kv)) {
	case let idx: size =>
		let v = m.items[idx].1;
		delete(m.items[idx]);
		resort(m);
		return v;
	case void =>
		return;
	};
};
// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	free(m.items);
	free(m);
};
use sort;

// Gets an item from a [[map]] by key, returning void if not found.
export fn get(m: *map, key: []u8) (*opaque | void) = {
	let dummy = 0;
	let probe = (key, (&dummy: *opaque));
	match (sort::search((m.items: []const opaque),
		size(([]u8, *opaque)),
		(&probe: const *opaque), &cmp_kv)) {
	case let idx: size =>
		return m.items[idx].1;
	case void =>
		return;
	};
};
use sort;

fn cmp_kv(a: const *opaque, b: const *opaque) int = {
	let ka = (*(a: *([]u8, *opaque))).0;
	let kb = (*(b: *([]u8, *opaque))).0;
	return keycmp(ka, kb);
};

fn keycmp(a: []u8, b: []u8) int = {
	let n = if (len(a) < len(b)) len(a) else len(b);
	for (let i = 0z; i < n; i += 1) {
		if (a[i] < b[i]) return -1;
		if (a[i] > b[i]) return 1;
	};
	if (len(a) < len(b)) return -1;
	if (len(a) > len(b)) return 1;
	return 0;
};

fn resort(m: *map) void = {
	sort::inplace((m.items: []opaque), size(([]u8, *opaque)), &cmp_kv);
};
// SPDX-License-Identifier: MPL-2.0

use ds::map;

// Sorted slice backed map from []u8 to *opaque.
// Keys are ordered byte-wize lexicgraphically.
//
// You are advised to create these with [[new]].
export type map = struct {
	vt: map::map,
	items: []([]u8, *opaque),
};

const _vt: map::vtable = map::vtable {
	getter   = &vt_get,
	setter   = &vt_set,
	deleter  = &vt_del,
	finisher = &vt_finish,
};

fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key);
fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v);
fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key);
fn vt_finish(m: *map::map) void = finish(m: *map);
// Creates a new [[map]].
export fn new() (*map | nomem) = {
	let m = alloc(map {
		vt = &_vt,
		items = [],
	})?;
	return m;
};
use sort;

// Sets an item in a [[map]], replacing any existing item with the same key.
export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	let dummy = 0;
	let probe = (key, (&dummy: *opaque));
	match (sort::search((m.items: []const opaque),
		size(([]u8, *opaque)),
		(&probe: const *opaque), &cmp_kv)) {
	case let idx: size =>
		m.items[idx].1 = value;
	case void =>
		append(m.items, (key, value))?;
	};
	resort(m);
};
map_swiss_siphash: key-value map implemented with Swiss tables and SipHash
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

use bytes;

// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	if (len(m.groups) == 0) return;
	let hv = hash64(m, key): u64;
	let t = h2(hv);
	let mask = m.group_mask;
	let off: size = (h1(hv): size) & mask;
	let idx: size = 0;

	for (true) {
		let g = &m.groups[off];
		for (let i = 0z; i < GROUP_SIZE; i += 1) {
			let c = g.ctrl[i];
			if (is_full_ctrl(c) && c == t) {
				if (bytes::equal(g.keys[i], key)) {
					let v = g.vals[i];
					g.ctrl[i] = CTRL_DELETED;
					g.keys[i] = [];
					g.vals[i] = null;
					m.used -= 1;
					m.tombs += 1;
					// elide the tombstones if exceed 1/3 of the capacity
					if (m.tombs * 3 >= capacity_slots(m)) {
						rehash_in_place(m);
					};
					match (v) {
					case null =>
						abort("map: null internal state escaped");
					case let p: *opaque =>
						return p;
					};
				};
			} else if (c == CTRL_EMPTY) {
				return;
			};
		};
		let next = probe_next(off, idx, mask);
		off = next.0;
		idx = next.1;
	};
};
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	if (len(m.groups) != 0) {
		free(m.groups);
	};
	free(m);
};
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

use bytes;

// Gets an item from a [[map]] by key, returning void if not found.
export fn get(m: *map, key: []u8) (*opaque | void) = {
	if (len(m.groups) == 0) return;
	let hv = hash64(m, key): u64;
	let t = h2(hv);
	let mask = m.group_mask;
	let off: size = (h1(hv): size) & mask;
	let idx: size = 0;

	for (true) {
		let g = &m.groups[off];
		for (let i = 0z; i < GROUP_SIZE; i += 1) {
			let c = g.ctrl[i];
			if (is_full_ctrl(c) && c == t) {
				if (bytes::equal(g.keys[i], key)) {
					match (g.vals[i]) {
					case null =>
						abort("map: null internal state escaped");
					case let p: *opaque =>
						return p;
					};
				};
			} else if (c == CTRL_EMPTY) {
				return;
			};
		};
		let next = probe_next(off, idx, mask);
		off = next.0;
		idx = next.1;
	};
};
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

use bytes;
use hash;
use hash::siphash;

export def GROUP_SIZE: size = 8z;
export def CTRL_EMPTY: u8 = 0x80;
export def CTRL_DELETED: u8 = 0xFE;

export type group = struct {
	ctrl: [GROUP_SIZE]u8,
	keys: [GROUP_SIZE][]u8,
	vals: [GROUP_SIZE]nullable *opaque,
};

fn group_set_empty(g: *group) void = {
	for (let i = 0z; i < GROUP_SIZE; i += 1) {
		g.ctrl[i] = CTRL_EMPTY;
		g.keys[i] = [];
		g.vals[i] = null;
	};
};

fn is_full_ctrl(c: u8) bool = (c & 0x80) == 0 && c != CTRL_DELETED;

fn hash64(m: *map, key: []u8) size = {
	let h = siphash::siphash(2, 4, &m.siphash_key);
	defer hash::close(&h);
	hash::write(&h, key);
	return siphash::sum(&h): size;
};

fn h1(h: u64) u64 = h >> 7u64;
fn h2(h: u64) u8 = (h & 0x7Fu64): u8;

fn probe_next(off: size, idx: size, mask: size) (size, size) = {
	let nidx = idx + 1;
	let noff = (off + nidx) & mask;
	return (noff, nidx);
};

fn capacity_slots(m: *map) size = (m.group_mask + 1) * GROUP_SIZE;

fn max_used_with_tombs(m: *map) size = {
	return (capacity_slots(m) * 7z) / 8z;
};

fn ensure_capacity_for_insert(m: *map) (void | nomem) = {
	if (m.used + m.tombs < max_used_with_tombs(m)) {
		return;
	};
	return resize(m, (m.group_mask + 1) * 2);
};

fn rehash_in_place(m: *map) void = {
	if (len(m.groups) == 0) return;
	let new_groups: []group = alloc([group{...}...], (m.group_mask + 1))!;
	for (let i = 0z; i < len(new_groups); i += 1) {
		group_set_empty(&new_groups[i]);
	};
	let old = m.groups;
	m.groups = new_groups;
	let old_groups = old;
	let old_mask = m.group_mask;
	m.used = 0;
	m.tombs = 0;

	for (let gi = 0z; gi <= old_mask; gi += 1) {
		let g = &old_groups[gi];
		for (let si = 0z; si < GROUP_SIZE; si += 1) {
			let c = g.ctrl[si];
			if (!is_full_ctrl(c)) continue;
			let k = g.keys[si];
			let v = g.vals[si];
			unchecked_put(m, k, v);
		};
	};
	free(old_groups);
};

fn resize(m: *map, new_groups_len: size) (void | nomem) = {
	if (new_groups_len == 0) new_groups_len = 1;
	let gs: []group = match (alloc([group{...}...], new_groups_len)) {
	case let a: []group => yield a;
	case nomem => return nomem;
	};
	for (let i = 0z; i < len(gs); i += 1) {
		group_set_empty(&gs[i]);
	};
	let old = m.groups;
	let old_mask = m.group_mask;
	m.groups = gs;
	m.group_mask = new_groups_len - 1;
	m.used = 0;
	m.tombs = 0;

	for (let gi = 0z; gi <= old_mask; gi += 1) {
		let g = &old[gi];
		for (let si = 0z; si < GROUP_SIZE; si += 1) {
			let c = g.ctrl[si];
			if (!is_full_ctrl(c)) continue;
			unchecked_put(m, g.keys[si], g.vals[si]);
		};
	};
	if (len(old) != 0) {
		free(old);
	};
};

fn unchecked_put(m: *map, key: []u8, val: nullable *opaque) void = {
	let hv = hash64(m, key): u64;
	let t = h2(hv);
	let mask = m.group_mask;
	let off: size = (h1(hv): size) & mask;
	let idx: size = 0;

	for (true) {
		let g = &m.groups[off];
		let first_dead: (size | void) = void;
		for (let i = 0z; i < GROUP_SIZE; i += 1) {
			let c = g.ctrl[i];
			if (is_full_ctrl(c)) {
				continue;
			} else if (c == CTRL_DELETED) {
				if (first_dead is void) first_dead = i;
			} else {
				let slot = match (first_dead) {
				case void => yield i;
				case let di: size => yield di;
				};
				g.keys[slot] = key;
				g.vals[slot] = val;
				g.ctrl[slot] = t;
				m.used += 1;
				if (slot == i) {
					void;
				} else {
					m.tombs -= 1;
				};
				return;
			};
		};
		let next = probe_next(off, idx, mask);
		off = next.0;
		idx = next.1;
	};
};
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

use ds::map;

// Swiss table based map from []u8 to *opaque.
//
// You are advised to create these with [[new]].
export type map = struct {
	vt: map::map,
	group_mask: size,
	used: size,
	tombs: size,
	siphash_key: [16]u8,
	groups: []group,
};

const _vt: map::vtable = map::vtable {
	getter   = &vt_get,
	setter   = &vt_set,
	deleter  = &vt_del,
	finisher = &vt_finish,
};

fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key);
fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v);
fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key);
fn vt_finish(m: *map::map) void = finish(m: *map);
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

use errors;
use ds::map;

// Creates a new [[map]] with an initial number of groups and SipHash key.
//
// n_groups must be greater than zero.
export fn new(n_groups: size, siphash_key: [16]u8) (*map | errors::invalid | nomem) = {
	if (n_groups == 0) {
		return errors::invalid;
	};

	let v: size = 1;
	for (v < n_groups) {
		v *= 2;
	};
	let groups_count = v;

	let gs: []group = match (alloc([group{...}...]: []group, groups_count)) {
	case let a: []group => yield a;
	case nomem => return nomem;
	};
	for (let i = 0z; i < len(gs); i += 1) {
		group_set_empty(&gs[i]);
	};

	let m = alloc(map {
		vt = &_vt,
		group_mask = groups_count - 1,
		used = 0,
		tombs = 0,
		siphash_key = siphash_key,
		groups = gs,
	})?;
	return m;
};
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

use bytes;

// Sets an item in a [[map]], replacing any existing item with the same key.
export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	let need_insert = true;

	if (len(m.groups) != 0) {
		let hv0 = hash64(m, key);
		let t0 = h2(hv0);
		let mask0 = m.group_mask;
		let off0: size = (h1(hv0): size) & mask0;
		let idx0: size = 0;

		need_insert = false;
		for (true) {
			let g = &m.groups[off0];

			for (let i = 0z; i < GROUP_SIZE; i += 1) {
				let c = g.ctrl[i];
				if (is_full_ctrl(c) && c == t0) {
					if (bytes::equal(g.keys[i], key)) {
						g.vals[i] = value;
						return;
					};
				} else if (c == CTRL_EMPTY) {
					need_insert = true;
					break;
				};
			};

			if (need_insert) {
				break;
			};

			let next = probe_next(off0, idx0, mask0);
			off0 = next.0;
			idx0 = next.1;
		};
	} else {
		need_insert = true;
	};

	if (!need_insert) {
		return;
	};

	match (ensure_capacity_for_insert(m)) {
	case void => yield;
	case nomem => return nomem;
	};

	let hv = hash64(m, key);
	let t = h2(hv);
	let mask = m.group_mask;
	let off: size = (h1(hv): size) & mask;
	let idx: size = 0;

	for (true) {
		let g = &m.groups[off];
		let first_dead: (size | void) = void;

		for (let i = 0z; i < GROUP_SIZE; i += 1) {
			let c = g.ctrl[i];
			if (is_full_ctrl(c)) {
				if (c == t && bytes::equal(g.keys[i], key)) {
					g.vals[i] = value;
					return;
				};
				continue;
			} else if (c == CTRL_DELETED) {
				if (first_dead is void) first_dead = i;
			} else {
				let slot = match (first_dead) {
				case void => yield i;
				case let di: size => yield di;
				};
				g.keys[slot] = key;
				g.vals[slot] = value;
				g.ctrl[slot] = t;
				m.used += 1;
				if (slot != i) {
					m.tombs -= 1;
				};
				return;
			};
		};

		let next = probe_next(off, idx, mask);
		off = next.0;
		idx = next.1;
	};
};
rbtree: red–black tree map
slice_basic: trivial linear search slice map
slice_sorted: sorted binary search slice map
swiss_siphash: SipHash swiss tables