Lindenii Project Forge
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