summaryrefslogtreecommitdiff
path: root/libmisc
diff options
context:
space:
mode:
Diffstat (limited to 'libmisc')
-rw-r--r--libmisc/CMakeLists.txt3
-rw-r--r--libmisc/include/libmisc/linkedlist.h46
-rw-r--r--libmisc/include/libmisc/macro.h13
-rw-r--r--libmisc/include/libmisc/map.h148
-rw-r--r--libmisc/include/libmisc/rand.h2
-rw-r--r--libmisc/linkedlist.c62
-rw-r--r--libmisc/map.c224
-rw-r--r--libmisc/tests/test_map.c60
-rw-r--r--libmisc/tests/test_private.c2
9 files changed, 557 insertions, 3 deletions
diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt
index 4599ead..fdbe949 100644
--- a/libmisc/CMakeLists.txt
+++ b/libmisc/CMakeLists.txt
@@ -8,7 +8,9 @@ target_include_directories(libmisc PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/
target_sources(libmisc INTERFACE
assert.c
intercept.c
+ linkedlist.c
log.c
+ map.c
)
target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>")
@@ -18,5 +20,6 @@ add_lib_test(libmisc test_endian)
add_lib_test(libmisc test_hash)
add_lib_test(libmisc test_log)
add_lib_test(libmisc test_macro)
+add_lib_test(libmisc test_map)
add_lib_test(libmisc test_private)
add_lib_test(libmisc test_rand)
diff --git a/libmisc/include/libmisc/linkedlist.h b/libmisc/include/libmisc/linkedlist.h
new file mode 100644
index 0000000..8adef66
--- /dev/null
+++ b/libmisc/include/libmisc/linkedlist.h
@@ -0,0 +1,46 @@
+/* libmisc/linkedlist.h - Singly- and doubly- linked lists
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_LINKEDLIST_H_
+#define _LIBMISC_LINKEDLIST_H_
+
+#include <libmisc/assert.h>
+#include <libmisc/macro.h>
+
+/* singly linked list *********************************************************/
+
+typedef struct _lm_sll_node {
+ struct _lm_sll_node *rear;
+} lm_sll_node;
+
+typedef struct {
+ lm_sll_node *front, *rear;
+} lm_sll_root;
+
+#define lm_sll_node_cast(node_typ, node_ptr) \
+ LM_CAST_FIELD_TO_STRUCT(node_typ, lm_sll_node, node_ptr)
+
+void lm_sll_push_to_rear(lm_sll_root *root, lm_sll_node *node);
+void lm_sll_pop_from_front(lm_sll_root *root);
+
+/* doubly linked list *********************************************************/
+
+typedef struct _lm_dll_node {
+ struct _lm_dll_node *front, *rear;
+} lm_dll_node;
+
+typedef struct {
+ lm_dll_node *front, *rear;
+} lm_dll_root;
+
+#define lm_dll_node_cast(node_typ, node_ptr) \
+ LM_CAST_FIELD_TO_STRUCT(node_typ, lm_dll_node, node_ptr)
+
+void lm_dll_push_to_rear(lm_dll_root *root, lm_dll_node *node);
+void lm_dll_remove(lm_dll_root *root, lm_dll_node *node);
+void lm_dll_pop_from_front(lm_dll_root *root);
+
+#endif /* _LIBMISC_LINKEDLIST_H_ */
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h
index b3e235c..6cb15fb 100644
--- a/libmisc/include/libmisc/macro.h
+++ b/libmisc/include/libmisc/macro.h
@@ -14,9 +14,20 @@
#define LM_NEVER_INLINE [[gnu::noinline]]
#define LM_FLATTEN [[gnu::flatten]]
-/* numeric */
+/* types */
#define LM_ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0]))
+
+#define LM_CAST_FIELD_TO_STRUCT(STRUCT_TYP, FIELD_NAME, PTR_TO_FIELD) ({ \
+ /* The _fptr assignment is to get the compiler to do type checking. */ \
+ typeof(((STRUCT_TYP *)NULL)->FIELD_NAME) *_fptr = (PTR_TO_FIELD); \
+ _fptr \
+ ? ((STRUCT_TYP*)( ((void*)_fptr) - offsetof(STRUCT_TYP, FIELD_NAME))) \
+ : NULL; \
+})
+
+/* numeric */
+
#define LM_CEILDIV(n, d) ( ((n)+(d)-1) / (d) ) /** Return ceil(n/d) */
#define LM_ROUND_UP(n, d) ( LM_CEILDIV(n, d) * (d) ) /** Return `n` rounded up to the nearest multiple of `d` */
#define LM_ROUND_DOWN(n, d) ( ((n)/(d)) * (d) ) /** Return `n` rounded down to the nearest multiple of `d` */
diff --git a/libmisc/include/libmisc/map.h b/libmisc/include/libmisc/map.h
new file mode 100644
index 0000000..f846f57
--- /dev/null
+++ b/libmisc/include/libmisc/map.h
@@ -0,0 +1,148 @@
+/* libmisc/map.h - A map/dict data structure
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_MAP_H_
+#define _LIBMISC_MAP_H_
+
+#include <stdbool.h>
+#include <stddef.h> /* for size_t */
+#include <stdint.h> /* for uint8_t */
+
+#include <libmisc/linkedlist.h>
+
+/* Type ***********************************************************************/
+
+struct _map {
+ size_t len;
+ size_t nbuckets;
+ lm_dll_root *buckets;
+
+ unsigned iterating;
+
+ size_t sizeof_kv;
+ size_t offsetof_k, sizeof_k;
+ size_t offsetof_v, sizeof_v;
+};
+
+/**
+ * MAP_DECLARE(MAPNAME, KEY_T, VAL_T) declares `struct MAPNAME`.
+ */
+#define MAP_DECLARE(MAPNAME, KEY_T, VAL_T) \
+ struct MAPNAME { \
+ struct _map core; \
+ struct { \
+ lm_dll_node; \
+ uint8_t flags; \
+ KEY_T key; \
+ VAL_T val; \
+ } kv_typ[0]; \
+ }
+
+#define _map_init(M) do { \
+ if (!(M)->core.sizeof_kv) { \
+ (M)->core.sizeof_kv = sizeof((M)->kv_typ[0]); \
+ (M)->core.sizeof_k = sizeof((M)->kv_typ[0].key); \
+ (M)->core.sizeof_v = sizeof((M)->kv_typ[0].val); \
+ (M)->core.offsetof_k = offsetof(typeof((M)->kv_typ[0]), key); \
+ (M)->core.offsetof_v = offsetof(typeof((M)->kv_typ[0]), val); \
+ } \
+} while(0)
+
+/* Methods ********************************************************************/
+
+/**
+ * map_len(map) returns the number of entries currently in `map`.
+ */
+#define map_len(M) ((M)->core.len)
+
+/**
+ * map_load(map, key) returns a pointer to the value in `map`
+ * associated with `key`, or else NULL.
+ */
+#define map_load(M, K) ({ \
+ _map_init(M); \
+ typeof((M)->kv_typ[0].key) _k = K; \
+ (typeof((M)->kv_typ[0].val)*)_map_load(&(M)->core, &_k); \
+})
+void *_map_load(struct _map *m, void *kp);
+
+/**
+ * map_del(map, key) ensures that `key` is not present in `map`.
+ * Returns whether `key` was in `map` before the call.
+ */
+#define map_del(M, K) ({ \
+ _map_init(M); \
+ typeof((M)->kv_typ[0].key) _k = K; \
+ _map_del(&(M)->core, &_k); \
+})
+bool _map_del(struct _map *m, void *kp);
+
+/**
+ * map_store(map, key, val) sets a value in the map. Returns a
+ * pointer to the map's copy of `val`.
+ */
+#define map_store(M, K, ...) ({ \
+ _map_init(M); \
+ typeof((M)->kv_typ[0].key) _k = K; \
+ typeof((M)->kv_typ[0].val) _v = __VA_ARGS__; \
+ (typeof((M)->kv_typ[0].val)*)_map_store(&(M)->core, &_k, &_v); \
+})
+void *_map_store(struct _map *m, void *kp, void *vp);
+
+/**
+ * map_free(map) frees the memory associated with the map.
+ */
+#define map_free(M) _map_free(&(M)->core);
+void _map_free(struct _map *m);
+
+/* Iteration ******************************************************************/
+
+struct _map_kv {
+ lm_dll_node;
+ uint8_t flags;
+};
+
+struct _map_iter {
+ struct _map *m;
+ void *keyp;
+ void **valpp;
+
+ size_t i;
+ struct _map_kv *kv;
+};
+
+/**
+ * MAP_FOREACH iterates over a map:
+ *
+ * MAP_FOREACH(MAP_EACH(MAP, key, valp)) {
+ * ...
+ * }
+ *
+ * It is safe to mutate the map with map_store() and, map_del() while
+ * iterating, but entries added by map_store() may or may not be
+ * visited by the iteration.
+ */
+#define MAP_FOREACH(M, KNAME, VNAME) _MAP_FOREACH(__COUNTER__, M, KNAME, VNAME)
+#define _MAP_FOREACH(CNT, M, KNAME, VNAME) \
+ for (bool _once_##CNT = true; _once_##CNT;) \
+ for (typeof((M)->kv_typ[0].key) KNAME; _once_##CNT;) \
+ for (typeof((M)->kv_typ[0].val) *VNAME; _once_##CNT;) \
+ for ( \
+ struct _map_iter _iter_##CNT = ({ \
+ _map_init(M); \
+ _map_iter_before(&(M)->core, &KNAME, (void**)&VNAME); \
+ }); \
+ _once_##CNT; \
+ ({ \
+ _once_##CNT = false; \
+ _map_iter_after(&_iter_##CNT); \
+ })) \
+ while (_map_iter_next(&_iter_##CNT))
+struct _map_iter _map_iter_before(struct _map *m, void *keyp, void **valpp);
+bool _map_iter_next(struct _map_iter *iter);
+void _map_iter_after(struct _map_iter *iter);
+
+#endif /* _LIBMISC_MAP_H_ */
diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h
index bb1ec0b..7ef238b 100644
--- a/libmisc/include/libmisc/rand.h
+++ b/libmisc/include/libmisc/rand.h
@@ -1,6 +1,6 @@
/* libmisc/rand.h - Non-crytpographic random-number utilities
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
diff --git a/libmisc/linkedlist.c b/libmisc/linkedlist.c
new file mode 100644
index 0000000..5fe0977
--- /dev/null
+++ b/libmisc/linkedlist.c
@@ -0,0 +1,62 @@
+/* libmisc/linkedlist.c - Singly- and doubly- linked lists
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stddef.h> /* for NULL */
+
+#include <libmisc/linkedlist.h>
+
+/* singly linked list *********************************************************/
+
+void lm_sll_push_to_rear(lm_sll_root *root, lm_sll_node *node) {
+ assert(root);
+ node->rear = NULL;
+ if (root->rear)
+ root->rear->rear = node;
+ else
+ root->front = node;
+ root->rear = node;
+}
+
+void lm_sll_pop_from_front(lm_sll_root *root) {
+ assert(root);
+ assert(root->front);
+ root->front = root->front->rear;
+ if (!root->front)
+ root->rear = NULL;
+}
+
+/* doubly linked list *********************************************************/
+
+void lm_dll_push_to_rear(lm_dll_root *root, lm_dll_node *node) {
+ assert(root);
+ assert(node);
+ node->front = root->rear;
+ node->rear = NULL;
+ if (root->rear)
+ root->rear->rear = node;
+ else
+ root->front = node;
+ root->rear = node;
+}
+
+void lm_dll_remove(lm_dll_root *root, lm_dll_node *node) {
+ assert(root);
+ assert(node);
+ if (node->front)
+ node->front->rear = node->rear;
+ else
+ root->front = node->rear;
+ if (node->rear)
+ node->rear->front = node->front;
+ else
+ root->rear = node->front;
+}
+
+void lm_dll_pop_from_front(lm_dll_root *root) {
+ assert(root);
+ assert(root->front);
+ lm_dll_remove(root, root->front);
+}
diff --git a/libmisc/map.c b/libmisc/map.c
new file mode 100644
index 0000000..bb8a2d2
--- /dev/null
+++ b/libmisc/map.c
@@ -0,0 +1,224 @@
+/* libmisc/map.c - A map/dict data structure
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <libmisc/hash.h>
+#include <libmisc/assert.h>
+#include <libmisc/map.h>
+
+#define FLAG_ITER (UINT8_C(1)<<0)
+#define FLAG_DEL (UINT8_C(1)<<1)
+
+/* Internal utilities *********************************************************/
+
+#define cast(n) lm_dll_node_cast(struct _map_kv, n)
+
+static inline void *_map_kv_keyp(struct _map *m, struct _map_kv *kv) {
+ assert(m);
+ assert(kv);
+ return ((void*)kv)+m->offsetof_k;
+}
+static inline void *_map_kv_valp(struct _map *m, struct _map_kv *kv) {
+ assert(m);
+ assert(kv);
+ return ((void*)kv)+m->offsetof_v;
+}
+
+static inline void _map_lookup(struct _map *m, void *keyp,
+ hash_t *ret_hash,
+ lm_dll_root **ret_bucket,
+ struct _map_kv **ret_kv) {
+ assert(m);
+ assert(keyp);
+ assert(ret_hash);
+ assert(ret_bucket);
+ assert(ret_kv);
+ *ret_hash = hash(keyp, m->sizeof_k);
+ if (m->nbuckets == 0) {
+ *ret_bucket = NULL;
+ *ret_kv = NULL;
+ return;
+ }
+ *ret_bucket = &m->buckets[*ret_hash % m->nbuckets];
+ for (struct _map_kv *kv = cast((*ret_bucket)->front); kv; kv = cast(kv->rear)) {
+ if (!(kv->flags & FLAG_DEL) &&
+ memcmp(_map_kv_keyp(m, kv), keyp, m->sizeof_k) == 0) {
+ *ret_kv = kv;
+ return;
+ }
+ }
+ *ret_kv = NULL;
+}
+
+static inline void _map_resize(struct _map *m, size_t new_nbuckets) {
+ assert(m);
+ assert(new_nbuckets);
+ lm_dll_root *new_buckets = calloc(new_nbuckets, sizeof(lm_dll_root));
+ for (size_t i = 0; i < m->nbuckets; i++) {
+ while (m->buckets[i].front) {
+ struct _map_kv *kv = cast(m->buckets[i].front);
+ lm_dll_pop_from_front(&m->buckets[i]);
+ hash_t h = hash(_map_kv_keyp(m, kv), m->sizeof_k);
+ lm_dll_push_to_rear(&new_buckets[h % new_nbuckets], kv);
+ }
+ }
+ m->nbuckets = new_nbuckets;
+ free(m->buckets);
+ m->buckets = new_buckets;
+}
+
+static bool _map_autoresize(struct _map *m) {
+ assert(m);
+ if (m->len > (m->nbuckets * 8/10)) {
+ size_t nbuckets = 1;
+ while (m->len > (nbuckets * 8/10))
+ nbuckets <<= 1;
+ _map_resize(m, nbuckets);
+ return true;
+ }
+ return false;
+}
+
+/* Methods ********************************************************************/
+
+void *_map_load(struct _map *m, void *keyp) {
+ assert(m);
+ assert(keyp);
+
+ hash_t h;
+ lm_dll_root *bucket;
+ struct _map_kv *kv;
+ _map_lookup(m, keyp, &h, &bucket, &kv);
+
+ if (!kv)
+ return NULL;
+ return _map_kv_valp(m, kv);
+}
+
+bool _map_del(struct _map *m, void *keyp) {
+ assert(m);
+ assert(keyp);
+
+ hash_t h;
+ lm_dll_root *bucket;
+ struct _map_kv *kv;
+ _map_lookup(m, keyp, &h, &bucket, &kv);
+
+ if (!kv)
+ return false;
+ if (kv->flags & FLAG_ITER) {
+ kv->flags |= FLAG_DEL;
+ } else {
+ lm_dll_remove(bucket, kv);
+ free(kv);
+ }
+ m->len--;
+ return true;
+}
+
+void *_map_store(struct _map *m, void *keyp, void *valp) {
+ assert(m);
+ assert(keyp);
+ assert(valp);
+
+ hash_t h;
+ lm_dll_root *bucket;
+ struct _map_kv *old;
+ _map_lookup(m, keyp, &h, &bucket, &old);
+
+ if (old) {
+ lm_dll_remove(bucket, old);
+ free(old);
+ m->len--;
+ }
+ m->len++;
+ if (!m->iterating && _map_autoresize(m)) {
+ h = hash(keyp, m->sizeof_k);
+ bucket = &m->buckets[h % m->nbuckets];
+ }
+ struct _map_kv *kv = calloc(1, m->sizeof_kv);
+ memcpy(_map_kv_keyp(m, kv), keyp, m->sizeof_k);
+ memcpy(_map_kv_valp(m, kv), valp, m->sizeof_v);
+ lm_dll_push_to_rear(bucket, kv);
+ return _map_kv_valp(m, kv);
+}
+
+void _map_free(struct _map *m) {
+ assert(m);
+
+ for (size_t i = 0; i < m->nbuckets; i++) {
+ while (m->buckets[i].front) {
+ struct _map_kv *kv = cast(m->buckets[i].front);
+ lm_dll_pop_from_front(&m->buckets[i]);
+ free(kv);
+ }
+ }
+ free(m->buckets);
+ m->len = 0;
+ m->nbuckets = 0;
+ m->buckets = NULL;
+}
+
+/* Iteration ******************************************************************/
+
+struct _map_iter _map_iter_before(struct _map *m, void *keyp, void **valpp) {
+ assert(m);
+ assert(keyp);
+ assert(valpp);
+
+ struct _map_iter state = {
+ .m = m,
+ .keyp = keyp,
+ .valpp = valpp,
+ };
+ m->iterating++;
+ return state;
+}
+
+void _map_iter_after(struct _map_iter *state) {
+ assert(state);
+ assert(state->m);
+
+ state->m->iterating--;
+ if (!state->m->iterating)
+ _map_autoresize(state->m);
+}
+
+bool _map_iter_next(struct _map_iter *state) {
+ assert(state);
+ assert(state->m);
+ assert(state->valpp);
+
+ if (!state->kv) {
+ if (!state->m->len)
+ return false;
+ while (!state->m->buckets[state->i].front)
+ state->i++;
+ state->kv = cast(state->m->buckets[state->i].front);
+ } else {
+ struct _map_kv *old_kv = state->kv;
+ state->kv = cast(old_kv->rear);
+
+ old_kv->flags &= ~FLAG_ITER;
+ if (old_kv->flags & FLAG_DEL) {
+ lm_dll_remove(&state->m->buckets[state->i], old_kv);
+ free(old_kv);
+ }
+
+ while (!state->kv) {
+ state->i++;
+ if (state->i == state->m->nbuckets)
+ return false;
+ state->kv = cast(state->m->buckets[state->i].front);
+ }
+ }
+ state->kv->flags |= FLAG_ITER;
+ memcpy(state->keyp, _map_kv_keyp(state->m, state->kv), state->m->sizeof_k);
+ *(state->valpp) = _map_kv_valp(state->m, state->kv);
+ return true;
+}
diff --git a/libmisc/tests/test_map.c b/libmisc/tests/test_map.c
new file mode 100644
index 0000000..855dace
--- /dev/null
+++ b/libmisc/tests/test_map.c
@@ -0,0 +1,60 @@
+/* libmisc/tests/test_map.c - Tests for <libmisc/map.h>
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/map.h>
+
+#include "test.h"
+
+MAP_DECLARE(intmap, int, int);
+
+int main() {
+ struct intmap m = {};
+ test_assert(map_len(&m) == 0);
+
+ int *v = map_store(&m, 3, 8);
+ test_assert(v && *v == 8);
+ test_assert(map_len(&m) == 1);
+
+ v = map_load(&m, 3);
+ test_assert(v);
+ test_assert(*v == 8);
+
+ v = NULL;
+
+ test_assert(map_del(&m, 3));
+ test_assert(map_len(&m) == 0);
+ test_assert(!map_del(&m, 3));
+
+ map_store(&m, 1, 11);
+ map_store(&m, 2, 12);
+ map_store(&m, 3, 13);
+ test_assert(map_len(&m) == 3);
+ bool seen_1 = false, seen_2 = false, seen_3 = false;
+ MAP_FOREACH(&m, ik, iv) {
+ switch (ik) {
+ case 1: seen_1 = true; break;
+ case 2: seen_2 = true; break;
+ case 3: seen_3 = true; break;
+ }
+ switch (ik) {
+ case 1: case 2: case 3:
+ map_store(&m, ik+20, (*iv)+20);
+ test_assert(map_del(&m, ik));
+ test_assert(!map_del(&m, ik));
+ test_assert(map_load(&m, ik) == NULL);
+ break;
+ }
+ }
+ test_assert(map_len(&m) == 3);
+ test_assert(seen_1); v = map_load(&m, 21); test_assert(v && *v == 31); v = map_load(&m, 1); test_assert(!v);
+ test_assert(seen_2); v = map_load(&m, 22); test_assert(v && *v == 32); v = map_load(&m, 2); test_assert(!v);
+ test_assert(seen_3); v = map_load(&m, 23); test_assert(v && *v == 33); v = map_load(&m, 3); test_assert(!v);
+
+ map_free(&m);
+ test_assert(map_len(&m) == 0);
+
+ return 0;
+}
diff --git a/libmisc/tests/test_private.c b/libmisc/tests/test_private.c
index 9b39932..024dddb 100644
--- a/libmisc/tests/test_private.c
+++ b/libmisc/tests/test_private.c
@@ -1,6 +1,6 @@
/* libmisc/tests/test_private.c - Tests for <libmisc/private.h>
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/