diff options
Diffstat (limited to 'libmisc')
-rw-r--r-- | libmisc/CMakeLists.txt | 3 | ||||
-rw-r--r-- | libmisc/include/libmisc/linkedlist.h | 46 | ||||
-rw-r--r-- | libmisc/include/libmisc/macro.h | 13 | ||||
-rw-r--r-- | libmisc/include/libmisc/map.h | 148 | ||||
-rw-r--r-- | libmisc/include/libmisc/rand.h | 2 | ||||
-rw-r--r-- | libmisc/linkedlist.c | 62 | ||||
-rw-r--r-- | libmisc/map.c | 224 | ||||
-rw-r--r-- | libmisc/tests/test_map.c | 60 | ||||
-rw-r--r-- | libmisc/tests/test_private.c | 2 |
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 */ |