From 572cf2c92f7fbd07d2c46d90fd3b2dc6c4c4bc3c Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Tue, 16 Sep 2025 18:51:53 +0800 Subject: [PATCH] Add map_splice_{basic,sorted} --- ds/map/map_slice_basic/README | 1 + ds/map/map_slice_basic/del.ha | 14 ++++++++++++++ ds/map/map_slice_basic/finish.ha | 7 +++++++ ds/map/map_slice_basic/get.ha | 12 ++++++++++++ ds/map/map_slice_basic/map.ha | 23 +++++++++++++++++++++++ ds/map/map_slice_basic/new.ha | 10 ++++++++++ ds/map/map_slice_basic/set.ha | 14 ++++++++++++++ ds/map/map_slice_basic/test.ha | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ds/map/map_slice_sorted/README | 1 + ds/map/map_slice_sorted/del.ha | 18 ++++++++++++++++++ ds/map/map_slice_sorted/finish.ha | 5 +++++ ds/map/map_slice_sorted/get.ha | 15 +++++++++++++++ ds/map/map_slice_sorted/internal.ha | 22 ++++++++++++++++++++++ ds/map/map_slice_sorted/map.ha | 24 ++++++++++++++++++++++++ ds/map/map_slice_sorted/new.ha | 8 ++++++++ ds/map/map_slice_sorted/set.ha | 16 ++++++++++++++++ ds/map/map_slice_sorted/test.ha | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/ds/map/map_slice_basic/README b/ds/map/map_slice_basic/README new file mode 100644 index 0000000000000000000000000000000000000000..41de4fe314bcc0de808dbf18a9169136a22fcf3d --- /dev/null +++ b/ds/map/map_slice_basic/README @@ -0,0 +1 @@ +map_slice_basic: trivial key-value map backed by a single slice diff --git a/ds/map/map_slice_basic/del.ha b/ds/map/map_slice_basic/del.ha new file mode 100644 index 0000000000000000000000000000000000000000..b10e179bc055eb5c9acc6492c96ac4f536920ebc --- /dev/null +++ b/ds/map/map_slice_basic/del.ha @@ -0,0 +1,14 @@ +// 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; + }; + }; +}; diff --git a/ds/map/map_slice_basic/finish.ha b/ds/map/map_slice_basic/finish.ha new file mode 100644 index 0000000000000000000000000000000000000000..d07898c0e7499d8080b5b1d5c93d08da417c0e42 --- /dev/null +++ b/ds/map/map_slice_basic/finish.ha @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 + +// Frees resources associated with a [[map]]. +export fn finish(m: *map) void = { + free(m.items); + free(m); +}; diff --git a/ds/map/map_slice_basic/get.ha b/ds/map/map_slice_basic/get.ha new file mode 100644 index 0000000000000000000000000000000000000000..b525a633cad21281bc8b0022731eeea47737cbc3 --- /dev/null +++ b/ds/map/map_slice_basic/get.ha @@ -0,0 +1,12 @@ +// 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; + }; + }; +}; diff --git a/ds/map/map_slice_basic/map.ha b/ds/map/map_slice_basic/map.ha new file mode 100644 index 0000000000000000000000000000000000000000..0f90fa037687dd0d39b51d491b59aba1699b178a --- /dev/null +++ b/ds/map/map_slice_basic/map.ha @@ -0,0 +1,23 @@ +// 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); diff --git a/ds/map/map_slice_basic/new.ha b/ds/map/map_slice_basic/new.ha new file mode 100644 index 0000000000000000000000000000000000000000..9fcb92f34a1cae581f631604d2468fc40d15631e --- /dev/null +++ b/ds/map/map_slice_basic/new.ha @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MPL-2.0 + +// Creates a new [[map]]. +export fn new() (*map | nomem) = { + let m = alloc(map { + vt = &_vt, + items = [], + })?; + return m; +}; diff --git a/ds/map/map_slice_basic/set.ha b/ds/map/map_slice_basic/set.ha new file mode 100644 index 0000000000000000000000000000000000000000..6915cb01feb73fe4e27747a4a287a143abd7bf75 --- /dev/null +++ b/ds/map/map_slice_basic/set.ha @@ -0,0 +1,14 @@ +// 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))?; +}; diff --git a/ds/map/map_slice_basic/test.ha b/ds/map/map_slice_basic/test.ha new file mode 100644 index 0000000000000000000000000000000000000000..12f94e8dda42d43bc0d41c35b1b104688d0813c0 --- /dev/null +++ b/ds/map/map_slice_basic/test.ha @@ -0,0 +1,68 @@ +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"); + }; +}; diff --git a/ds/map/map_slice_sorted/README b/ds/map/map_slice_sorted/README new file mode 100644 index 0000000000000000000000000000000000000000..72e729adaab89eeaeafd79ad73b2013434d1b707 --- /dev/null +++ b/ds/map/map_slice_sorted/README @@ -0,0 +1 @@ +map_slice_sorted: sorted slice key-value store with binary search diff --git a/ds/map/map_slice_sorted/del.ha b/ds/map/map_slice_sorted/del.ha new file mode 100644 index 0000000000000000000000000000000000000000..6f72396a359e7d2be7d765cd38362f5e02cc340d --- /dev/null +++ b/ds/map/map_slice_sorted/del.ha @@ -0,0 +1,18 @@ +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; + }; +}; diff --git a/ds/map/map_slice_sorted/finish.ha b/ds/map/map_slice_sorted/finish.ha new file mode 100644 index 0000000000000000000000000000000000000000..88e8eb85f2404ed13519653348e4bf92ba86e217 --- /dev/null +++ b/ds/map/map_slice_sorted/finish.ha @@ -0,0 +1,5 @@ +// Frees resources associated with a [[map]]. +export fn finish(m: *map) void = { + free(m.items); + free(m); +}; diff --git a/ds/map/map_slice_sorted/get.ha b/ds/map/map_slice_sorted/get.ha new file mode 100644 index 0000000000000000000000000000000000000000..c440b04e46d816ec942765c003c432e5ae97b8a5 --- /dev/null +++ b/ds/map/map_slice_sorted/get.ha @@ -0,0 +1,15 @@ +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; + }; +}; diff --git a/ds/map/map_slice_sorted/internal.ha b/ds/map/map_slice_sorted/internal.ha new file mode 100644 index 0000000000000000000000000000000000000000..63b55343c764d933b5e32ff3b3865a5824128828 --- /dev/null +++ b/ds/map/map_slice_sorted/internal.ha @@ -0,0 +1,22 @@ +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); +}; diff --git a/ds/map/map_slice_sorted/map.ha b/ds/map/map_slice_sorted/map.ha new file mode 100644 index 0000000000000000000000000000000000000000..1f819253f4c88af0ff68f19e70bfc2d82e4609fc --- /dev/null +++ b/ds/map/map_slice_sorted/map.ha @@ -0,0 +1,24 @@ +// 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); diff --git a/ds/map/map_slice_sorted/new.ha b/ds/map/map_slice_sorted/new.ha new file mode 100644 index 0000000000000000000000000000000000000000..ad6b9b2e62926e0d3ace63f5a478266d12452800 --- /dev/null +++ b/ds/map/map_slice_sorted/new.ha @@ -0,0 +1,8 @@ +// Creates a new [[map]]. +export fn new() (*map | nomem) = { + let m = alloc(map { + vt = &_vt, + items = [], + })?; + return m; +}; diff --git a/ds/map/map_slice_sorted/set.ha b/ds/map/map_slice_sorted/set.ha new file mode 100644 index 0000000000000000000000000000000000000000..f38b3ae13a67094c1b76adf908fea236f1192718 --- /dev/null +++ b/ds/map/map_slice_sorted/set.ha @@ -0,0 +1,16 @@ +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); +}; diff --git a/ds/map/map_slice_sorted/test.ha b/ds/map/map_slice_sorted/test.ha new file mode 100644 index 0000000000000000000000000000000000000000..12f94e8dda42d43bc0d41c35b1b104688d0813c0 --- /dev/null +++ b/ds/map/map_slice_sorted/test.ha @@ -0,0 +1,68 @@ +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"); + }; +}; -- 2.48.1