Lindenii Project Forge
Login

hare-ds

Data structures for Hare
Commit info
ID
572cf2c92f7fbd07d2c46d90fd3b2dc6c4c4bc3c
Author
Runxi Yu <me@runxiyu.org>
Author date
Tue, 16 Sep 2025 18:51:53 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Tue, 16 Sep 2025 18:51:53 +0800
Actions
Add map_splice_{basic,sorted}
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))?;
};
use errors;
use strings;
use ds::map;

@test fn roundtrip() void = {
	const key: [16]u8 = [0...];

	let m: *map = match (new()) {
	case let p: *map => yield p;
	case nomem => abort("unexpected nomem");
	};
	defer finish(m);

	let v1 = 1, v2 = 2, v3 = 3;
	let p1: *opaque = (&v1: *opaque);
	let p2: *opaque = (&v2: *opaque);
	let p3: *opaque = (&v3: *opaque);

	let k1 = strings::toutf8("alpha");
	let k2 = strings::toutf8("beta");
	let k3 = strings::toutf8("gamma");

	match (map::set(m, k1, p1)) {
	case void => yield;
	case nomem => abort("unexpected nomem in set(k1,p1)");
	};

	match (map::get(m, k1)) {
	case let got: *opaque =>
		assert(got == p1, "get(k1) must return p1");
	case void =>
		abort("get(k1) unexpectedly void");
	};

	match (map::set(m, k1, p2)) {
	case void => yield;
	case nomem => abort("unexpected nomem in replace");
	};
	match (map::get(m, k1)) {
	case let got: *opaque =>
		assert(got == p2, "replace must overwrite prior value");
	case void =>
		abort("get(k1) void after replace");
	};

	match (map::set(m, k2, p3)) {
	case void => yield;
	case nomem => abort("unexpected nomem in set(k2,p3)");
	};

	match (map::get(m, k3)) {
	case void => yield;
	case *opaque =>
		abort("get(k3) must be void for missing key");
	};

	match (map::del(m, k2)) {
	case let got: *opaque =>
		assert(got == p3, "del(k2) must return stored value");
	case void =>
		abort("del(k2) unexpectedly void");
	};
	match (map::del(m, k2)) {
	case void => yield;
	case *opaque =>
		abort("del(k2) must be void after prior delete");
	};
};
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);
};
use errors;
use strings;
use ds::map;

@test fn roundtrip() void = {
	const key: [16]u8 = [0...];

	let m: *map = match (new()) {
	case let p: *map => yield p;
	case nomem => abort("unexpected nomem");
	};
	defer finish(m);

	let v1 = 1, v2 = 2, v3 = 3;
	let p1: *opaque = (&v1: *opaque);
	let p2: *opaque = (&v2: *opaque);
	let p3: *opaque = (&v3: *opaque);

	let k1 = strings::toutf8("alpha");
	let k2 = strings::toutf8("beta");
	let k3 = strings::toutf8("gamma");

	match (map::set(m, k1, p1)) {
	case void => yield;
	case nomem => abort("unexpected nomem in set(k1,p1)");
	};

	match (map::get(m, k1)) {
	case let got: *opaque =>
		assert(got == p1, "get(k1) must return p1");
	case void =>
		abort("get(k1) unexpectedly void");
	};

	match (map::set(m, k1, p2)) {
	case void => yield;
	case nomem => abort("unexpected nomem in replace");
	};
	match (map::get(m, k1)) {
	case let got: *opaque =>
		assert(got == p2, "replace must overwrite prior value");
	case void =>
		abort("get(k1) void after replace");
	};

	match (map::set(m, k2, p3)) {
	case void => yield;
	case nomem => abort("unexpected nomem in set(k2,p3)");
	};

	match (map::get(m, k3)) {
	case void => yield;
	case *opaque =>
		abort("get(k3) must be void for missing key");
	};

	match (map::del(m, k2)) {
	case let got: *opaque =>
		assert(got == p3, "del(k2) must return stored value");
	case void =>
		abort("del(k2) unexpectedly void");
	};
	match (map::del(m, k2)) {
	case void => yield;
	case *opaque =>
		abort("del(k2) must be void after prior delete");
	};
};