summaryrefslogtreecommitdiff
path: root/libmisc
diff options
context:
space:
mode:
Diffstat (limited to 'libmisc')
-rw-r--r--libmisc/CMakeLists.txt21
-rw-r--r--libmisc/assert.c30
l---------libmisc/include/assert.h1
-rw-r--r--libmisc/include/libmisc/_intercept.h27
-rw-r--r--libmisc/include/libmisc/assert.h24
-rw-r--r--libmisc/include/libmisc/endian.h84
-rw-r--r--libmisc/include/libmisc/linkedlist.h46
-rw-r--r--libmisc/include/libmisc/log.h34
-rw-r--r--libmisc/include/libmisc/macro.h107
-rw-r--r--libmisc/include/libmisc/private.h38
-rw-r--r--libmisc/include/libmisc/rand.h46
-rw-r--r--libmisc/include/libmisc/vcall.h27
-rw-r--r--libmisc/intercept.c34
-rw-r--r--libmisc/linkedlist.c62
-rw-r--r--libmisc/log.c273
-rw-r--r--libmisc/tests/test.h21
-rw-r--r--libmisc/tests/test_assert.c94
-rw-r--r--libmisc/tests/test_assert_min.c17
-rw-r--r--libmisc/tests/test_endian.c41
-rw-r--r--libmisc/tests/test_hash.c30
-rw-r--r--libmisc/tests/test_log.c70
-rw-r--r--libmisc/tests/test_macro.c54
-rw-r--r--libmisc/tests/test_private.c29
-rw-r--r--libmisc/tests/test_rand.c79
24 files changed, 1228 insertions, 61 deletions
diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt
index 0c5c019..c80e060 100644
--- a/libmisc/CMakeLists.txt
+++ b/libmisc/CMakeLists.txt
@@ -1,8 +1,23 @@
-# libmisc/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions
+# libmisc/CMakeLists.txt - Low-level C programming utilities; sort of an augmented "libc"
#
-# 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
add_library(libmisc INTERFACE)
-target_include_directories(libmisc SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_include_directories(libmisc PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_sources(libmisc INTERFACE
+ assert.c
+ intercept.c
+ linkedlist.c
+ log.c
+)
target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>")
+
+add_lib_test(libmisc test_assert)
+add_lib_test(libmisc test_assert_min)
+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_private)
+add_lib_test(libmisc test_rand)
diff --git a/libmisc/assert.c b/libmisc/assert.c
new file mode 100644
index 0000000..fdd8154
--- /dev/null
+++ b/libmisc/assert.c
@@ -0,0 +1,30 @@
+/* libmisc/assert.c - More assertions
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdbool.h> /* for bool, true, false */
+
+#define LOG_NAME ASSERT
+#include <libmisc/log.h> /* for errorf() */
+
+#include <libmisc/assert.h>
+
+#ifndef NDEBUG
+#define __lm_printf __lm_light_printf
+void __assert_msg_fail(const char *expr,
+ const char *file, unsigned int line, const char *func,
+ const char *msg) {
+ static bool in_fail = false;
+ if (!in_fail) {
+ in_fail = true;
+ errorf("%s:%u:%s(): assertion \"%s\" failed%s%s",
+ file, line, func,
+ expr,
+ msg ? ": " : "", msg ?: "");
+ in_fail = false;
+ }
+ __lm_abort();
+}
+#endif
diff --git a/libmisc/include/assert.h b/libmisc/include/assert.h
new file mode 120000
index 0000000..8473e36
--- /dev/null
+++ b/libmisc/include/assert.h
@@ -0,0 +1 @@
+libmisc/assert.h \ No newline at end of file
diff --git a/libmisc/include/libmisc/_intercept.h b/libmisc/include/libmisc/_intercept.h
new file mode 100644
index 0000000..a264144
--- /dev/null
+++ b/libmisc/include/libmisc/_intercept.h
@@ -0,0 +1,27 @@
+/* libmisc/_intercept.h - Interceptable ("weak") functions
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC__INTERCEPT_H_
+#define _LIBMISC__INTERCEPT_H_
+
+/* pico-sdk/newlib define these to be [[gnu:weak]] already, but
+ * depending on optimization options glibc might not, and GCC might
+ * assume it knows what they do and optimize them out. So define our
+ * own `__lm_` wrappers that GCC/glibc won't interfere with.
+ */
+
+[[gnu::format(printf, 1, 2)]]
+int __lm_printf(const char *format, ...);
+
+[[noreturn]] void __lm_abort(void);
+
+/* __lm_light_printf is expected to have less stack use than regular
+ * __lm_printf, if possible. */
+
+[[gnu::format(printf, 1, 2)]]
+int __lm_light_printf(const char *format, ...);
+
+#endif /* _LIBMISC__INTERCEPT_H_ */
diff --git a/libmisc/include/libmisc/assert.h b/libmisc/include/libmisc/assert.h
new file mode 100644
index 0000000..8cf0735
--- /dev/null
+++ b/libmisc/include/libmisc/assert.h
@@ -0,0 +1,24 @@
+/* libmisc/assert.h - More assertions
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_ASSERT_H_
+#define _LIBMISC_ASSERT_H_
+
+#ifdef NDEBUG
+# define __assert_msg(expr, expr_str, msg) ((void)0)
+#else
+# define __assert_msg(expr, expr_str, msg) do { if (!(expr)) __assert_msg_fail(expr_str, __FILE__, __LINE__, __func__, msg); } while (0)
+[[noreturn]] void __assert_msg_fail(const char *expr,
+ const char *file, unsigned int line, const char *func,
+ const char *msg);
+#endif
+
+#define assert_msg(expr, msg) __assert_msg(expr, #expr, msg) /* libmisc */
+#define assert(expr) __assert_msg(expr, #expr, 0) /* C89, POSIX-2001 */
+#define assert_notreached(msg) do { __assert_msg(0, "notreached", msg); __builtin_unreachable(); } while (0) /* libmisc */
+#define static_assert _Static_assert /* C11 */
+
+#endif /* _LIBMISC_ASSERT_H_ */
diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h
index 24d7d42..75240fe 100644
--- a/libmisc/include/libmisc/endian.h
+++ b/libmisc/include/libmisc/endian.h
@@ -1,15 +1,17 @@
/* libmisc/endian.h - Endian-conversion helpers
*
- * 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
*/
#ifndef _LIBMISC_ENDIAN_H_
#define _LIBMISC_ENDIAN_H_
-#include <assert.h>
+#include <stddef.h> /* for size_t */
#include <stdint.h> /* for uint{n}_t */
+#include <libmisc/assert.h>
+
/* Big endian *****************************************************************/
typedef struct {
@@ -70,6 +72,45 @@ static inline uint32_t uint32be_unmarshal(uint32be_t in) {
return uint32be_decode(in.octets);
}
+typedef struct {
+ uint8_t octets[8];
+} uint64be_t;
+static_assert(sizeof(uint64be_t) == 8);
+
+static inline size_t uint64be_encode(uint8_t *out, uint64_t in) {
+ out[0] = (uint8_t)((in >> 56) & 0xFF);
+ out[1] = (uint8_t)((in >> 48) & 0xFF);
+ out[2] = (uint8_t)((in >> 40) & 0xFF);
+ out[3] = (uint8_t)((in >> 32) & 0xFF);
+ out[4] = (uint8_t)((in >> 24) & 0xFF);
+ out[5] = (uint8_t)((in >> 16) & 0xFF);
+ out[6] = (uint8_t)((in >> 8) & 0xFF);
+ out[7] = (uint8_t)((in >> 0) & 0xFF);
+ return 8;
+}
+
+static inline uint64_t uint64be_decode(uint8_t *in) {
+ return (((uint64_t)(in[0])) << 56)
+ | (((uint64_t)(in[1])) << 48)
+ | (((uint64_t)(in[2])) << 40)
+ | (((uint64_t)(in[3])) << 32)
+ | (((uint64_t)(in[4])) << 24)
+ | (((uint64_t)(in[5])) << 16)
+ | (((uint64_t)(in[6])) << 8)
+ | (((uint64_t)(in[7])) << 0)
+ ;
+}
+
+static inline uint64be_t uint64be_marshal(uint64_t in) {
+ uint64be_t out;
+ uint64be_encode(out.octets, in);
+ return out;
+}
+
+static inline uint64_t uint64be_unmarshal(uint64be_t in) {
+ return uint64be_decode(in.octets);
+}
+
/* Little endian **************************************************************/
typedef struct {
@@ -130,4 +171,43 @@ static inline uint32_t uint32le_unmarshal(uint32le_t in) {
return uint32le_decode(in.octets);
}
+typedef struct {
+ uint8_t octets[8];
+} uint64le_t;
+static_assert(sizeof(uint64le_t) == 8);
+
+static inline size_t uint64le_encode(uint8_t *out, uint64_t in) {
+ out[0] = (uint8_t)((in >> 0) & 0xFF);
+ out[1] = (uint8_t)((in >> 8) & 0xFF);
+ out[2] = (uint8_t)((in >> 16) & 0xFF);
+ out[3] = (uint8_t)((in >> 24) & 0xFF);
+ out[4] = (uint8_t)((in >> 32) & 0xFF);
+ out[5] = (uint8_t)((in >> 40) & 0xFF);
+ out[6] = (uint8_t)((in >> 48) & 0xFF);
+ out[7] = (uint8_t)((in >> 56) & 0xFF);
+ return 8;
+}
+
+static inline uint64_t uint64le_decode(uint8_t *in) {
+ return (((uint64_t)(in[0])) << 0)
+ | (((uint64_t)(in[1])) << 8)
+ | (((uint64_t)(in[2])) << 16)
+ | (((uint64_t)(in[3])) << 24)
+ | (((uint64_t)(in[4])) << 32)
+ | (((uint64_t)(in[5])) << 40)
+ | (((uint64_t)(in[6])) << 48)
+ | (((uint64_t)(in[7])) << 56)
+ ;
+}
+
+static inline uint64le_t uint64le_marshal(uint64_t in) {
+ uint64le_t out;
+ uint64le_encode(out.octets, in);
+ return out;
+}
+
+static inline uint64_t uint64le_unmarshal(uint64le_t in) {
+ return uint64le_decode(in.octets);
+}
+
#endif /* _LIBMISC_ENDIAN_H_ */
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/log.h b/libmisc/include/libmisc/log.h
new file mode 100644
index 0000000..79c0ab6
--- /dev/null
+++ b/libmisc/include/libmisc/log.h
@@ -0,0 +1,34 @@
+/* libmisc/log.h - stdio logging
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_LOG_H_
+#define _LIBMISC_LOG_H_
+
+#include <stdint.h> /* for uint8_t */
+
+#include <libmisc/macro.h>
+#include <libmisc/_intercept.h>
+
+#ifdef NDEBUG
+ #define _LOG_NDEBUG 1
+#else
+ #define _LOG_NDEBUG 0
+#endif
+
+const char *const_byte_str(uint8_t b);
+
+#define n_errorf(nam, fmt, ...) do { __lm_printf("error: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+#define n_infof(nam, fmt, ...) do { __lm_printf("info : " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+#define n_debugf(nam, fmt, ...) do { if (LM_CAT3_(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \
+ __lm_printf("debug: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+
+#endif /* _LIBMISC_LOG_H_ */
+
+#if defined(LOG_NAME) && !defined(errorf)
+#define errorf(fmt, ...) n_errorf(LOG_NAME, fmt, __VA_ARGS__)
+#define infof(fmt, ...) n_infof(LOG_NAME, fmt, __VA_ARGS__)
+#define debugf(fmt, ...) n_debugf(LOG_NAME, fmt, __VA_ARGS__)
+#endif
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h
new file mode 100644
index 0000000..6cb15fb
--- /dev/null
+++ b/libmisc/include/libmisc/macro.h
@@ -0,0 +1,107 @@
+/* libmisc/macro.h - Useful C preprocessor macros
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_MACRO_H_
+#define _LIBMISC_MACRO_H_
+
+/* for function definitions */
+
+#define LM_UNUSED(argname)
+#define LM_ALWAYS_INLINE [[gnu::always_inline]] inline
+#define LM_NEVER_INLINE [[gnu::noinline]]
+#define LM_FLATTEN [[gnu::flatten]]
+
+/* 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` */
+#define LM_NEXT_POWER_OF_2(x) ( (x) ? 1ULL<<((sizeof(unsigned long long)*8)-__builtin_clzll(x)) : 1) /** Return the lowest power of 2 that is > x */
+#define LM_FLOORLOG2(x) ((sizeof(unsigned long long)*8)-__builtin_clzll(x)-1) /** Return floor(log_2(x) */
+
+/* strings */
+
+#define LM_STR(x) #x
+#define LM_STR_(x) LM_STR(x)
+
+/* token pasting */
+
+#define LM_CAT2(a, b) a ## b
+#define LM_CAT3(a, b, c) a ## b ## c
+
+#define LM_CAT2_(a, b) LM_CAT2(a, b)
+#define LM_CAT3_(a, b, c) LM_CAT3(a, b, c)
+
+/* macro arguments */
+
+#define LM_FIRST(a, ...) a
+#define LM_SECOND(a, b, ...) b
+#define LM_EAT(...)
+#define LM_EXPAND(...) __VA_ARGS__
+
+/* conditionals */
+
+#define LM_T xxTxx
+#define LM_F xxFxx
+
+#define LM_SENTINEL() bogus, LM_T /* a magic sentinel value */
+#define LM_IS_SENTINEL(...) LM_SECOND(__VA_ARGS__, LM_F)
+
+#define LM_IF(cond) LM_CAT2(_LM_IF__, cond) /* LM_IF(cond)(then)(else) */
+#define _LM_IF__xxTxx(...) __VA_ARGS__ LM_EAT
+#define _LM_IF__xxFxx(...) LM_EXPAND
+
+/* tuples */
+
+#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x)
+#define _LM_IS_TUPLE(...) LM_SENTINEL()
+
+/* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */
+#define _LM_TUPLES_COMMA(tuple...) (tuple),
+#define LM_TUPLES_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples)
+#define LM_TUPLES_HEAD(tuples) LM_EXPAND(LM_FIRST LM_EAT() (_LM_TUPLES_COMMA tuples))
+#define LM_TUPLES_TAIL(tuples) LM_EAT tuples
+
+/* iteration */
+
+/* BUG: LM_FOREACH_TUPLE maxes out at 1024 tuples. */
+#define LM_FOREACH_TUPLE(tuples, func, ...) \
+ _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__))
+#define _LM_FOREACH_TUPLE(tuples, func, ...) \
+ LM_IF(LM_TUPLES_NONEMPTY(tuples))( \
+ _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \
+ _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \
+ )()
+#define _LM_FOREACH_TUPLE_indirect() _LM_FOREACH_TUPLE
+
+#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()()
+
+#define _LM_EVAL(...) _LM_EVAL__1024(__VA_ARGS__) /* 1024 iterations aught to be enough for anybody */
+#define _LM_EVAL__1024(...) _LM_EVAL__512(_LM_EVAL__512(__VA_ARGS__))
+#define _LM_EVAL__512(...) _LM_EVAL__256(_LM_EVAL__256(__VA_ARGS__))
+#define _LM_EVAL__256(...) _LM_EVAL__128(_LM_EVAL__128(__VA_ARGS__))
+#define _LM_EVAL__128(...) _LM_EVAL__64(_LM_EVAL__64(__VA_ARGS__))
+#define _LM_EVAL__64(...) _LM_EVAL__32(_LM_EVAL__32(__VA_ARGS__))
+#define _LM_EVAL__32(...) _LM_EVAL__16(_LM_EVAL__16(__VA_ARGS__))
+#define _LM_EVAL__16(...) _LM_EVAL__8(_LM_EVAL__8(__VA_ARGS__))
+#define _LM_EVAL__8(...) _LM_EVAL__4(_LM_EVAL__4(__VA_ARGS__))
+#define _LM_EVAL__4(...) _LM_EVAL__2(_LM_EVAL__2(__VA_ARGS__))
+#define _LM_EVAL__2(...) _LM_EVAL__1(_LM_EVAL__1(__VA_ARGS__))
+#define _LM_EVAL__1(...) __VA_ARGS__
+
+#endif /* _LIBMISC_MACRO_H_ */
diff --git a/libmisc/include/libmisc/private.h b/libmisc/include/libmisc/private.h
index 0eb8c42..5518d1f 100644
--- a/libmisc/include/libmisc/private.h
+++ b/libmisc/include/libmisc/private.h
@@ -1,37 +1,17 @@
/* libmisc/private.h - Utilities to hide struct fields
*
- * 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
*/
-#ifndef _LIBMISC_CPPUTIL_H_
-#define _LIBMISC_CPPUTIL_H_
+#ifndef _LIBMISC_PRIVATE_H_
+#define _LIBMISC_PRIVATE_H_
-/* primitive utilities */
+#include <libmisc/macro.h>
-#define _SECOND(a, b, ...) b
+#define YES LM_SENTINEL()
+#define IS_IMPLEMENTATION_FOR(name) LM_IS_SENTINEL(IMPLEMENTATION_FOR_##name)
+#define BEGIN_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))()(struct {) struct {} LM_CAT2_(_PRIVATE_FORCE_SEMICOLON_, __COUNTER__)
+#define END_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))(struct {} LM_CAT2_(_PRIVATE_FORCE_SEMICOLON_, __COUNTER__))(} LM_CAT2_(_PRIVATE_, __COUNTER__))
-#define _CAT(a, b) a ## b
-#define _CAT2(a, b) _CAT(a, b)
-#define _EAT(...)
-#define _EXPAND(...) __VA_ARGS__
-
-/* conditionals */
-
-#define _T xxTxx
-#define _F xxFxx
-
-#define _SENTINEL() bogus, _T /* a magic sentinel value */
-#define _IS_SENTINEL(...) _SECOND(__VA_ARGS__, _F)
-
-#define _IF(cond) _CAT(_IF__, cond) /* _IF(cond)(then)(else) */
-#define _IF__xxTxx(...) __VA_ARGS__ _EAT
-#define _IF__xxFxx(...) _EXPAND
-
-/* high-level functionality */
-
-#define YES _SENTINEL()
-#define BEGIN_PRIVATE(name) _IF(_IS_SENTINEL(IMPLEMENTATION_FOR_##name))()(struct {)
-#define END_PRIVATE(name) _IF(_IS_SENTINEL(IMPLEMENTATION_FOR_##name))()(} _CAT2(_HIDDEN_, __COUNTER__);)
-
-#endif /* _LIBMISC_CPPUTIL_H_ */
+#endif /* _LIBMISC_PRIVATE_H_ */
diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h
new file mode 100644
index 0000000..7ef238b
--- /dev/null
+++ b/libmisc/include/libmisc/rand.h
@@ -0,0 +1,46 @@
+/* libmisc/rand.h - Non-crytpographic random-number utilities
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_RAND_H_
+#define _LIBMISC_RAND_H_
+
+#include <stdint.h> /* for uint{n}_t, UINT{n}_C() */
+#include <stdlib.h> /* for random() */
+
+#include <libmisc/assert.h>
+
+/**
+ * Return a psuedo-random number in the half-open interval [0,cnt).
+ * `cnt` must not be greater than 1<<63.
+ */
+static inline uint64_t rand_uint63n(uint64_t cnt) {
+ assert(cnt != 0 && ((cnt-1) & 0x8000000000000000) == 0);
+ if (cnt <= UINT64_C(1)<<31) {
+ uint32_t fair_cnt = ((UINT32_C(1)<<31) / cnt) * cnt;
+ uint32_t rnd;
+ do {
+ rnd = random();
+ } while (rnd >= fair_cnt);
+ return rnd % cnt;
+ } else if (cnt <= UINT64_C(1)<<62) {
+ uint64_t fair_cnt = ((UINT64_C(1)<<62) / cnt) * cnt;
+ uint64_t rnd;
+ do {
+ rnd = (((uint64_t)random()) << 31) | random();
+ } while (rnd >= fair_cnt);
+ return rnd % cnt;
+ } else if (cnt <= UINT64_C(1)<<63) {
+ uint64_t fair_cnt = ((UINT64_C(1)<<63) / cnt) * cnt;
+ uint64_t rnd;
+ do {
+ rnd = (((uint64_t)random()) << 62) | (((uint64_t)random()) << 31) | random();
+ } while (rnd >= fair_cnt);
+ return rnd % cnt;
+ }
+ assert_notreached("cnt is out of bounds");
+}
+
+#endif /* _LIBMISC_RAND_H_ */
diff --git a/libmisc/include/libmisc/vcall.h b/libmisc/include/libmisc/vcall.h
deleted file mode 100644
index ea9402e..0000000
--- a/libmisc/include/libmisc/vcall.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* libmisc/vcall.h - A simple Go-ish object system built on GCC -fplan9-extensions
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#ifndef _LIBMISC_VCALL_H_
-#define _LIBMISC_VCALL_H_
-
-#include <assert.h> /* for assert() and static_assert() */
-#include <stddef.h> /* for offsetof() */
-
-#define VCALL(o, m, ...) \
- ({ \
- assert(o); \
- (o)->vtable->m(o __VA_OPT__(,) __VA_ARGS__); \
- })
-
-#define VCALL_SELF(obj_typ, iface_typ, iface_ptr) \
- ({ \
- static_assert(_Generic(iface_ptr, iface_typ *: 1, default: 0), \
- "typeof("#iface_ptr") != "#iface_typ); \
- assert(iface_ptr); \
- ((obj_typ*)(((void*)iface_ptr)-offsetof(obj_typ,iface_typ))); \
- })
-
-#endif /* _LIBMISC_VCALL_H_ */
diff --git a/libmisc/intercept.c b/libmisc/intercept.c
new file mode 100644
index 0000000..85a3801
--- /dev/null
+++ b/libmisc/intercept.c
@@ -0,0 +1,34 @@
+/* libmisc/intercept.c - Interceptable ("weak") functions
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdarg.h> /* for va_list, va_start(), va_end() */
+#include <stdio.h> /* for vprintf() */
+#include <stdlib.h> /* for abort() */
+
+#include <libmisc/_intercept.h>
+
+[[gnu::weak]]
+int __lm_printf(const char *format, ...) {
+ va_list va;
+ va_start(va, format);
+ int ret = vprintf(format, va);
+ va_end(va);
+ return ret;
+}
+
+[[gnu::weak]]
+int __lm_light_printf(const char *format, ...) {
+ va_list va;
+ va_start(va, format);
+ int ret = vprintf(format, va);
+ va_end(va);
+ return ret;
+}
+
+[[gnu::weak]]
+void __lm_abort(void) {
+ abort();
+}
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/log.c b/libmisc/log.c
new file mode 100644
index 0000000..be87de6
--- /dev/null
+++ b/libmisc/log.c
@@ -0,0 +1,273 @@
+/* libmisc/log.c - stdio logging
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdint.h> /* for uint{n}_t */
+
+#include <libmisc/assert.h> /* for static_assert() */
+
+static const char *byte_strs[] = {
+ "0x00",
+ "0x01",
+ "0x02",
+ "0x03",
+ "0x04",
+ "0x05",
+ "0x06",
+ "0x07",
+ "0x08",
+ "0x09",
+ "0x0A",
+ "0x0B",
+ "0x0C",
+ "0x0D",
+ "0x0E",
+ "0x0F",
+ "0x10",
+ "0x11",
+ "0x12",
+ "0x13",
+ "0x14",
+ "0x15",
+ "0x16",
+ "0x17",
+ "0x18",
+ "0x19",
+ "0x1A",
+ "0x1B",
+ "0x1C",
+ "0x1D",
+ "0x1E",
+ "0x1F",
+ "0x20",
+ "0x21",
+ "0x22",
+ "0x23",
+ "0x24",
+ "0x25",
+ "0x26",
+ "0x27",
+ "0x28",
+ "0x29",
+ "0x2A",
+ "0x2B",
+ "0x2C",
+ "0x2D",
+ "0x2E",
+ "0x2F",
+ "0x30",
+ "0x31",
+ "0x32",
+ "0x33",
+ "0x34",
+ "0x35",
+ "0x36",
+ "0x37",
+ "0x38",
+ "0x39",
+ "0x3A",
+ "0x3B",
+ "0x3C",
+ "0x3D",
+ "0x3E",
+ "0x3F",
+ "0x40",
+ "0x41",
+ "0x42",
+ "0x43",
+ "0x44",
+ "0x45",
+ "0x46",
+ "0x47",
+ "0x48",
+ "0x49",
+ "0x4A",
+ "0x4B",
+ "0x4C",
+ "0x4D",
+ "0x4E",
+ "0x4F",
+ "0x50",
+ "0x51",
+ "0x52",
+ "0x53",
+ "0x54",
+ "0x55",
+ "0x56",
+ "0x57",
+ "0x58",
+ "0x59",
+ "0x5A",
+ "0x5B",
+ "0x5C",
+ "0x5D",
+ "0x5E",
+ "0x5F",
+ "0x60",
+ "0x61",
+ "0x62",
+ "0x63",
+ "0x64",
+ "0x65",
+ "0x66",
+ "0x67",
+ "0x68",
+ "0x69",
+ "0x6A",
+ "0x6B",
+ "0x6C",
+ "0x6D",
+ "0x6E",
+ "0x6F",
+ "0x70",
+ "0x71",
+ "0x72",
+ "0x73",
+ "0x74",
+ "0x75",
+ "0x76",
+ "0x77",
+ "0x78",
+ "0x79",
+ "0x7A",
+ "0x7B",
+ "0x7C",
+ "0x7D",
+ "0x7E",
+ "0x7F",
+ "0x80",
+ "0x81",
+ "0x82",
+ "0x83",
+ "0x84",
+ "0x85",
+ "0x86",
+ "0x87",
+ "0x88",
+ "0x89",
+ "0x8A",
+ "0x8B",
+ "0x8C",
+ "0x8D",
+ "0x8E",
+ "0x8F",
+ "0x90",
+ "0x91",
+ "0x92",
+ "0x93",
+ "0x94",
+ "0x95",
+ "0x96",
+ "0x97",
+ "0x98",
+ "0x99",
+ "0x9A",
+ "0x9B",
+ "0x9C",
+ "0x9D",
+ "0x9E",
+ "0x9F",
+ "0xA0",
+ "0xA1",
+ "0xA2",
+ "0xA3",
+ "0xA4",
+ "0xA5",
+ "0xA6",
+ "0xA7",
+ "0xA8",
+ "0xA9",
+ "0xAA",
+ "0xAB",
+ "0xAC",
+ "0xAD",
+ "0xAE",
+ "0xAF",
+ "0xB0",
+ "0xB1",
+ "0xB2",
+ "0xB3",
+ "0xB4",
+ "0xB5",
+ "0xB6",
+ "0xB7",
+ "0xB8",
+ "0xB9",
+ "0xBA",
+ "0xBB",
+ "0xBC",
+ "0xBD",
+ "0xBE",
+ "0xBF",
+ "0xC0",
+ "0xC1",
+ "0xC2",
+ "0xC3",
+ "0xC4",
+ "0xC5",
+ "0xC6",
+ "0xC7",
+ "0xC8",
+ "0xC9",
+ "0xCA",
+ "0xCB",
+ "0xCC",
+ "0xCD",
+ "0xCE",
+ "0xCF",
+ "0xD0",
+ "0xD1",
+ "0xD2",
+ "0xD3",
+ "0xD4",
+ "0xD5",
+ "0xD6",
+ "0xD7",
+ "0xD8",
+ "0xD9",
+ "0xDA",
+ "0xDB",
+ "0xDC",
+ "0xDD",
+ "0xDE",
+ "0xDF",
+ "0xE0",
+ "0xE1",
+ "0xE2",
+ "0xE3",
+ "0xE4",
+ "0xE5",
+ "0xE6",
+ "0xE7",
+ "0xE8",
+ "0xE9",
+ "0xEA",
+ "0xEB",
+ "0xEC",
+ "0xED",
+ "0xEE",
+ "0xEF",
+ "0xF0",
+ "0xF1",
+ "0xF2",
+ "0xF3",
+ "0xF4",
+ "0xF5",
+ "0xF6",
+ "0xF7",
+ "0xF8",
+ "0xF9",
+ "0xFA",
+ "0xFB",
+ "0xFC",
+ "0xFD",
+ "0xFE",
+ "0xFF",
+};
+static_assert(sizeof(byte_strs)/sizeof(byte_strs[0]) == 0x100);
+
+const char *const_byte_str(uint8_t b) {
+ return byte_strs[b];
+}
diff --git a/libmisc/tests/test.h b/libmisc/tests/test.h
new file mode 100644
index 0000000..ea13d3c
--- /dev/null
+++ b/libmisc/tests/test.h
@@ -0,0 +1,21 @@
+/* libmisc/tests/test.h - Common test utilities
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_TESTS_TEST_H_
+#define _LIBMISC_TESTS_TEST_H_
+
+#include <stdio.h>
+#include <stdlib.h> /* for exit() */
+
+#define test_assert(expr) do { \
+ if (!(expr)) { \
+ printf("test failure: %s:%d:%s: %s\n", \
+ __FILE__, __LINE__, __func__, #expr); \
+ exit(1); \
+ } \
+ } while (0)
+
+#endif /* _LIBMISC_TESTS_TEST_H_ */
diff --git a/libmisc/tests/test_assert.c b/libmisc/tests/test_assert.c
new file mode 100644
index 0000000..15f9446
--- /dev/null
+++ b/libmisc/tests/test_assert.c
@@ -0,0 +1,94 @@
+/* libmisc/tests/test_assert.c - Tests for <libmisc/assert.h>
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <setjmp.h>
+#include <stdarg.h> /* for va_list, va_start(), va_end() */
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libmisc/macro.h>
+#include <libmisc/assert.h>
+#include <libmisc/_intercept.h>
+
+#include "test.h"
+
+/* Intercept failures and logging *********************************************/
+
+bool global_failed;
+char *global_log;
+jmp_buf global_env;
+
+#define with_intercept() ({ \
+ global_failed = false; \
+ if (global_log) \
+ free(global_log); \
+ global_log = NULL; \
+ setjmp(global_env) == 0; \
+ })
+
+void __lm_abort(void) {
+ global_failed = true;
+ longjmp(global_env, 1);
+}
+
+int __lm_light_printf(const char *format, ...) {
+ va_list va;
+ va_start(va, format);
+ int ret = vasprintf(&global_log, format, va);
+ va_end(va);
+ return ret;
+}
+
+#define __builtin_unreachable() test_assert(0)
+
+/* Utilities ******************************************************************/
+
+#define test_should_succeed(test) do { \
+ if (with_intercept()) { \
+ test; \
+ } \
+ test_assert(global_failed == false); \
+ test_assert(global_log == NULL); \
+ } while (0)
+
+#define test_should_fail(test, exp_log) do { \
+ if (with_intercept()) { \
+ test; \
+ } \
+ test_assert(global_failed == true); \
+ if (!(global_log != NULL && \
+ strcmp(global_log, exp_log) == 0)) { \
+ printf("exp = \"%s\"\n" \
+ "act = \"%s\"\n", \
+ exp_log, global_log); \
+ test_assert(0); \
+ } \
+ } while (0)
+
+/* Actual tests ***************************************************************/
+
+static_assert(sizeof(char) == 1);
+
+int main() {
+#ifndef NDEBUG
+ test_should_succeed(assert(true));
+ test_should_fail(assert(false), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed\n");
+
+ test_should_succeed(assert_msg(true, "foo"));
+ test_should_fail(assert_msg(false, "foo"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed: foo\n");
+ test_should_succeed(assert_msg(true, NULL));
+ test_should_fail(assert_msg(false, NULL), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed\n");
+
+ test_should_fail(assert_notreached("xxx"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"notreached\" failed: xxx\n");
+
+ if (global_log) {
+ free(global_log);
+ global_log = NULL;
+ }
+#endif
+ return 0;
+}
diff --git a/libmisc/tests/test_assert_min.c b/libmisc/tests/test_assert_min.c
new file mode 100644
index 0000000..9c0394b
--- /dev/null
+++ b/libmisc/tests/test_assert_min.c
@@ -0,0 +1,17 @@
+/* libmisc/tests/test_assert_min.c - Tests for minimal <libmisc/assert.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+/* Don't include *anything* else. */
+#include <libmisc/assert.h>
+
+static_assert(1 == 1);
+
+int main() {
+ assert_msg(1, "foo");
+ assert(1);
+ return 0;
+ assert_notreached("ret");
+}
diff --git a/libmisc/tests/test_endian.c b/libmisc/tests/test_endian.c
new file mode 100644
index 0000000..dcb3cc2
--- /dev/null
+++ b/libmisc/tests/test_endian.c
@@ -0,0 +1,41 @@
+/* libmisc/tests/test_endian.c - Tests for <libmisc/endian.h>
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <string.h> /* for memcmp() */
+
+#include <libmisc/endian.h>
+
+#include "test.h"
+
+int main() {
+ uint8_t act[(2+4+8)*2] = {0};
+ size_t pos = 0;
+ pos += uint16be_encode(&act[pos], UINT16_C(0x1234));
+ pos += uint32be_encode(&act[pos], UINT32_C(0x56789ABC));
+ pos += uint64be_encode(&act[pos], UINT64_C(0xAC589A93278CB30A));
+ pos += uint16le_encode(&act[pos], UINT16_C(0x1234));
+ pos += uint32le_encode(&act[pos], UINT32_C(0x56789ABC));
+ pos += uint64le_encode(&act[pos], UINT64_C(0xAC589A93278CB30A));
+
+ test_assert(pos == sizeof(act));
+ uint8_t exp[(2+4+8)*2] = { 0x12, 0x34,
+ 0x56, 0x78, 0x9A, 0xBC,
+ 0xAC, 0x58, 0x9A, 0x93, 0x27, 0x8C, 0xB3, 0x0A,
+ 0x34, 0x12,
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x0A, 0xB3, 0x8C, 0x27, 0x93, 0x9A, 0x58, 0xAC};
+ test_assert(memcmp(act, exp, sizeof(act)) == 0);
+
+ pos = 0;
+ test_assert(uint16be_decode(&act[pos]) == UINT16_C(0x1234)); pos += 2;
+ test_assert(uint32be_decode(&act[pos]) == UINT32_C(0x56789ABC)); pos += 4;
+ test_assert(uint64be_decode(&act[pos]) == UINT64_C(0xAC589A93278CB30A)); pos += 8;
+ test_assert(uint16le_decode(&act[pos]) == UINT16_C(0x1234)); pos += 2;
+ test_assert(uint32le_decode(&act[pos]) == UINT32_C(0x56789ABC)); pos += 4;
+ test_assert(uint64le_decode(&act[pos]) == UINT64_C(0xAC589A93278CB30A)); pos += 8;
+
+ return 0;
+}
diff --git a/libmisc/tests/test_hash.c b/libmisc/tests/test_hash.c
new file mode 100644
index 0000000..c1af385
--- /dev/null
+++ b/libmisc/tests/test_hash.c
@@ -0,0 +1,30 @@
+/* libmisc/tests/test_hash.c - Tests for <libmisc/hash.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/hash.h>
+
+#include "test.h"
+
+int main() {
+ test_assert(hash("hello", sizeof("hello")) == hash("hello", sizeof("hello")));
+ test_assert(hash("hello", sizeof("hello")) != hash("Hello", sizeof("Hello")));
+ int one = 1;
+ int two = 2;
+ test_assert(hash(&one, sizeof(int)) != hash("hello", sizeof("hello")));
+ test_assert(hash(&one, sizeof(int)) == hash(&one, sizeof(int)));
+ test_assert(hash(&one, sizeof(int)) != hash(&two, sizeof(int)));
+
+ hash_t myhash;
+ hash_init(&myhash);
+ hash_write(&myhash, "hello", 5);
+ test_assert(myhash == hash("hello", 5));
+ hash_t myhash_a = myhash;
+ hash_write(&myhash, "world", 5);
+ test_assert(myhash != myhash_a);
+ test_assert(myhash == hash("helloworld", 10));
+
+ return 0;
+}
diff --git a/libmisc/tests/test_log.c b/libmisc/tests/test_log.c
new file mode 100644
index 0000000..49a76ca
--- /dev/null
+++ b/libmisc/tests/test_log.c
@@ -0,0 +1,70 @@
+/* libmisc/tests/test_log.c - Tests for <libmisc/log.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#define _GNU_SOURCE /* for vasprintf() */
+#include <stdarg.h> /* for va_list */
+#include <stdio.h> /* for vasprintf() */
+#include <stdlib.h> /* for free() */
+#include <string.h> /* for strcmp() */
+
+#define LOG_NAME FROBNICATE
+#include <libmisc/log.h>
+
+#include <libmisc/_intercept.h>
+
+#include "test.h"
+
+/* Intercept output ***********************************************************/
+
+static char *log_output = NULL;
+
+int __lm_printf(const char *format, ...) {
+ va_list va;
+ va_start(va, format);
+ int ret = vasprintf(&log_output, format, va);
+ va_end(va);
+ return ret;
+}
+
+/* Actual tests ***************************************************************/
+
+#define should_print(_exp, cmd) do { \
+ char *exp = _exp; \
+ test_assert(!log_output); \
+ cmd; \
+ if (!exp) \
+ test_assert(!log_output); \
+ else { \
+ test_assert(log_output); \
+ if (strcmp(log_output, exp)) { \
+ printf("exp = \"%s\"\n" \
+ "act = \"%s\"\n", \
+ exp, log_output); \
+ test_assert(0); \
+ } \
+ } \
+ if (log_output) { \
+ free(log_output); \
+ log_output = NULL; \
+ } \
+ } while (0)
+
+int main() {
+ should_print("error: FROBNICATE: val=42\n",
+ errorf("val=%d", 42));
+ should_print("info : FROBNICATE: val=0\n",
+ infof("val=%d", 0));
+#ifndef NDEBUG
+#define CONFIG_FROBNICATE_DEBUG 1
+ should_print("debug: FROBNICATE: val=-2\n",
+ debugf("val=%d", -2));
+#undef CONFIG_FROBNICATE_DEBUG
+#define CONFIG_FROBNICATE_DEBUG 0
+ should_print(NULL,
+ debugf("val=%d", -2));
+#endif
+ return 0;
+}
diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c
new file mode 100644
index 0000000..1320eb3
--- /dev/null
+++ b/libmisc/tests/test_macro.c
@@ -0,0 +1,54 @@
+/* libmisc/tests/test_macro.c - Tests for <libmisc/macro.h>
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/macro.h>
+
+#include "test.h"
+
+int main() {
+ printf("== LM_NEXT_POWER_OF_2 =====================================\n");
+ /* valid down to 0. */
+ test_assert(LM_NEXT_POWER_OF_2(0) == 1);
+ test_assert(LM_NEXT_POWER_OF_2(1) == 2);
+ test_assert(LM_NEXT_POWER_OF_2(2) == 4);
+ test_assert(LM_NEXT_POWER_OF_2(3) == 4);
+ test_assert(LM_NEXT_POWER_OF_2(4) == 8);
+ test_assert(LM_NEXT_POWER_OF_2(5) == 8);
+ test_assert(LM_NEXT_POWER_OF_2(6) == 8);
+ test_assert(LM_NEXT_POWER_OF_2(7) == 8);
+ test_assert(LM_NEXT_POWER_OF_2(8) == 16);
+ /* ... */
+ test_assert(LM_NEXT_POWER_OF_2(16) == 32);
+ /* ... */
+ test_assert(LM_NEXT_POWER_OF_2(0x7000000000000000) == 0x8000000000000000);
+ /* ... */
+ test_assert(LM_NEXT_POWER_OF_2(0x8000000000000000-1) == 0x8000000000000000);
+ /* Valid up to 0x8000000000000000-1 = (1<<63)-1 */
+
+ printf("== LM_FLOORLOG2 ===========================================\n");
+ /* valid down to 1. */
+ test_assert(LM_FLOORLOG2(1) == 0);
+ test_assert(LM_FLOORLOG2(2) == 1);
+ test_assert(LM_FLOORLOG2(3) == 1);
+ test_assert(LM_FLOORLOG2(4) == 2);
+ test_assert(LM_FLOORLOG2(5) == 2);
+ test_assert(LM_FLOORLOG2(6) == 2);
+ test_assert(LM_FLOORLOG2(7) == 2);
+ test_assert(LM_FLOORLOG2(8) == 3);
+ /* ... */
+ test_assert(LM_FLOORLOG2(16) == 4);
+ /* ... */
+ test_assert(LM_FLOORLOG2(0x80000000) == 31);
+ /* ... */
+ test_assert(LM_FLOORLOG2(0xFFFFFFFF) == 31);
+ test_assert(LM_FLOORLOG2(0x100000000) == 32);
+ /* ... */
+ test_assert(LM_FLOORLOG2(0x8000000000000000) == 63);
+ /* ... */
+ test_assert(LM_FLOORLOG2(0xFFFFFFFFFFFFFFFF) == 63);
+
+ return 0;
+}
diff --git a/libmisc/tests/test_private.c b/libmisc/tests/test_private.c
new file mode 100644
index 0000000..024dddb
--- /dev/null
+++ b/libmisc/tests/test_private.c
@@ -0,0 +1,29 @@
+/* libmisc/tests/test_private.c - Tests for <libmisc/private.h>
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/private.h>
+
+struct a {
+ int foo;
+ BEGIN_PRIVATE(A);
+ int bar;
+ END_PRIVATE(A);
+};
+
+#define IMPLEMENTATION_FOR_B YES
+
+struct b {
+ int foo;
+ BEGIN_PRIVATE(B);
+ int bar;
+ END_PRIVATE(B);
+};
+
+int main() {
+ struct b obj;
+ obj.bar = 0;
+ return obj.bar;
+}
diff --git a/libmisc/tests/test_rand.c b/libmisc/tests/test_rand.c
new file mode 100644
index 0000000..8076155
--- /dev/null
+++ b/libmisc/tests/test_rand.c
@@ -0,0 +1,79 @@
+/* libmisc/tests/test_rand.c - Tests for <libmisc/rand.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdbool.h>
+#include <setjmp.h>
+
+#include <libmisc/rand.h>
+#include <libmisc/_intercept.h>
+
+#include "test.h"
+
+/* Intercept failures *********************************************************/
+
+#ifndef NDEBUG
+jmp_buf *__catch;
+
+void __lm_abort(void) {
+ if (__catch)
+ longjmp(*__catch, 1);
+ abort();
+}
+
+#define should_abort(cmd) do { \
+ jmp_buf *old_catch = __catch; \
+ jmp_buf env; \
+ __catch = &env; \
+ if (!setjmp(env)) { \
+ cmd; \
+ __catch = old_catch; \
+ test_assert(false); \
+ } else { \
+ __catch = old_catch; \
+ } \
+ } while (0);
+#endif
+
+/* Actual tests ***************************************************************/
+
+#define ROUNDS 4096
+#define MAX_SEE_ALL 128
+
+static void test_n(uint64_t cnt) {
+ if (cnt == 0 || cnt > UINT64_C(1)<<63) {
+#ifndef NDEBUG
+ should_abort(rand_uint63n(cnt));
+#else
+ return;
+#endif
+ } else {
+ double sum = 0;
+ bool seen[MAX_SEE_ALL] = {0};
+ for (int i = 0; i < ROUNDS; i++) {
+ uint64_t val = rand_uint63n(cnt);
+ sum += val;
+ test_assert(val < cnt);
+ if (cnt < MAX_SEE_ALL)
+ seen[val] = true;
+ }
+ if (cnt > 1) {
+ test_assert(sum/ROUNDS > 0.45*(cnt-1));
+ test_assert(sum/ROUNDS < 0.55*(cnt-1));
+ }
+ if (cnt < MAX_SEE_ALL) {
+ for (uint64_t i = 0; i < cnt; i++)
+ test_assert(seen[i]);
+ }
+ }
+}
+
+int main() {
+ for (uint8_t i = 0; i < 64; i++)
+ test_n(UINT64_C(1)<<i);
+ for (uint64_t j = 0; j < MAX_SEE_ALL; j++)
+ test_n(j);
+ return 0;
+}