Lindenii Project Forge
Login

hare-ds

Data structures for Hare
Commit info
ID
396e6d0e882aebafedddc97f39eae3efd6561cce
Author
Runxi Yu <me@runxiyu.org>
Author date
Tue, 16 Sep 2025 23:40:58 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Wed, 17 Sep 2025 00:16:57 +0800
Actions
Add a generic hashmap implementation
// 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) = {
	let b = m.buckets[hash64(m, key) % m.n];
	return map::del(b, key);
	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 = {
	for (let i = 0z; i < m.n; i += 1) {
		map::finish(m.buckets[i]);
	};
	free(m.buckets);
	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) = {
	let b = m.buckets[hash64(m, key) % m.n];
	return map::get(b, key);
	return map::get(m.inner, key);
};
use hash;
use hash::fnv;

fn hash64(m: *map, key: []u8) size = {
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 SipHash for
// 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,
	n: size,
	buckets: []*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;

// 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) = {
	if (n == 0) {
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 =>
		yield (hm: *map::map);
	case errors::invalid =>
		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;
		};
	case nomem =>
		return nomem;
	};

	let m = match (alloc(map {
		vt = &_vt,
		n = n,
		buckets = buckets,
		inner = inner,
	})) {
	case let pm: *map => yield pm;
	case let p: *map => yield p;
	case nomem =>
		for (let j = 0z; j < len(buckets); j += 1) {
			map::finish(buckets[j]);
		};
		free(buckets);
		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) = {
	let b = m.buckets[hash64(m, key) % m.n];
	return map::set(b, key, value);
	return map::set(m.inner, key, value);
};
map_hashmap: key-value map implemented as a 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);
};
// 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[hash64(m, key) % m.n];
	return map::del(b, key);
	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 = {
	for (let i = 0z; i < m.n; i += 1) {
		map::finish(m.buckets[i]);
	};
	free(m.buckets);
	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 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[hash64(m, key) % m.n];
	return map::get(b, key);
	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(m: *map, key: []u8) size = {
	let h = siphash::siphash(2, 4, &m.siphash_key);
fn hash64(params: nullable *opaque, key: []u8) size = {
	let keyptr = match (params) {
	case null =>
		abort("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,
	n: size,
	siphash_key: [16]u8,
	buckets: []*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 bytes;
use errors;
use hash;
use hash::siphash;
use ds::map;
use ds::map::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) = {
	if (n == 0) {
		return errors::invalid;
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 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 inner = match (map_hashmap::new(
		make_fallback, n, &hash64, (keybox: *opaque),
	)) {
	case let hm: *map_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,
		n = n,
		siphash_key = siphash_key,
		buckets = buckets,
		inner = inner,
		key = keybox,
	})) {
	case let pm: *map => yield pm;
	case let p: *map => yield p;
	case nomem =>
		for (let j = 0z; j < len(buckets); j += 1) {
			map::finish(buckets[j]);
		};
		free(buckets);
		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 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[hash64(m, key) % m.n];
	return map::set(b, key, value);
	return map::set(m.inner, key, value);
};