diff options
Diffstat (limited to 'libmisc')
43 files changed, 3433 insertions, 451 deletions
diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt index 02b19d5..9bb282b 100644 --- a/libmisc/CMakeLists.txt +++ b/libmisc/CMakeLists.txt @@ -1,20 +1,39 @@ -# 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 + endian.c + error.c + error_generated.c + fmt.c + hash.c + intercept.c + linkedlist.c log.c + map.c + rand.c + utf8.c +) + +target_compile_options(libmisc INTERFACE + -no-integrated-cpp + -wrapper "${CMAKE_CURRENT_SOURCE_DIR}/wrap-cc" ) -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_fmt) 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_obj) +add_lib_test(libmisc test_obj_nest) add_lib_test(libmisc test_private) add_lib_test(libmisc test_rand) -add_lib_test(libmisc test_vcall) diff --git a/libmisc/assert.c b/libmisc/assert.c index 8231c85..410ec21 100644 --- a/libmisc/assert.c +++ b/libmisc/assert.c @@ -1,31 +1,26 @@ /* libmisc/assert.c - More assertions * - * 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 */ -#include <stdbool.h> /* for bool, true, false */ -#include <stdlib.h> /* for abort() */ - #define LOG_NAME ASSERT -#include <libmisc/log.h> /* for errorf() */ +#include <libmisc/log.h> /* for log_errorln() */ #include <libmisc/assert.h> #ifndef NDEBUG -[[noreturn, gnu::weak]] void __assert_msg_fail(const char *expr, - const char *file, unsigned int line, const char *func, - const char *msg) { + 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 ?: ""); + log_errorln(file, ":", line, ":", func, "(): ", + "assertion ", (qstr, expr), " failed", + msg ? ": " : "", msg ?: ""); in_fail = false; } - abort(); + __lm_abort(); } #endif diff --git a/libmisc/endian.c b/libmisc/endian.c new file mode 100644 index 0000000..2528f48 --- /dev/null +++ b/libmisc/endian.c @@ -0,0 +1,136 @@ +/* libmisc/endian.c - Endian-conversion helpers + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/macro.h> /* for LM_FORCE_SEMICOLON */ + +#include <libmisc/endian.h> + +#define endian_declare_wrappers(NBIT, ENDIAN) \ + uint##NBIT##ENDIAN##_t uint##NBIT##ENDIAN##_marshal(uint##NBIT##_t in) { \ + uint##NBIT##ENDIAN##_t out; \ + uint##NBIT##ENDIAN##_encode(out.octets, in); \ + return out; \ + } \ + uint##NBIT##_t uint##NBIT##ENDIAN##_unmarshal(uint##NBIT##ENDIAN##_t in) { \ + return uint##NBIT##ENDIAN##_decode(in.octets); \ + } \ + LM_FORCE_SEMICOLON + +/* Big endian *****************************************************************/ + +size_t uint16be_encode(uint8_t *out, uint16_t in) { + out[0] = (uint8_t)((in >> 8) & 0xFF); + out[1] = (uint8_t)((in >> 0) & 0xFF); + return 2; +} + +uint16_t uint16be_decode(uint8_t *in) { + return (((uint16_t)(in[0])) << 8) + | (((uint16_t)(in[1])) << 0) + ; +} + +size_t uint32be_encode(uint8_t *out, uint32_t in) { + out[0] = (uint8_t)((in >> 24) & 0xFF); + out[1] = (uint8_t)((in >> 16) & 0xFF); + out[2] = (uint8_t)((in >> 8) & 0xFF); + out[3] = (uint8_t)((in >> 0) & 0xFF); + return 4; +} + +uint32_t uint32be_decode(uint8_t *in) { + return (((uint32_t)(in[0])) << 24) + | (((uint32_t)(in[1])) << 16) + | (((uint32_t)(in[2])) << 8) + | (((uint32_t)(in[3])) << 0) + ; +} + +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; +} + +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) + ; +} + +endian_declare_wrappers(16, be); +endian_declare_wrappers(32, be); +endian_declare_wrappers(64, be); + +/* Little endian **************************************************************/ + +size_t uint16le_encode(uint8_t *out, uint16_t in) { + out[0] = (uint8_t)((in >> 0) & 0xFF); + out[1] = (uint8_t)((in >> 8) & 0xFF); + return 2; +} + +uint16_t uint16le_decode(uint8_t *in) { + return (((uint16_t)(in[0])) << 0) + | (((uint16_t)(in[1])) << 8) + ; +} + +size_t uint32le_encode(uint8_t *out, uint32_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); + return 4; +} + +uint32_t uint32le_decode(uint8_t *in) { + return (((uint32_t)(in[0])) << 0) + | (((uint32_t)(in[1])) << 8) + | (((uint32_t)(in[2])) << 16) + | (((uint32_t)(in[3])) << 24) + ; +} + +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; +} + +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) + ; +} + +endian_declare_wrappers(16, le); +endian_declare_wrappers(32, le); +endian_declare_wrappers(64, le); diff --git a/libmisc/error.c b/libmisc/error.c new file mode 100644 index 0000000..dfe4e80 --- /dev/null +++ b/libmisc/error.c @@ -0,0 +1,26 @@ +/* libmisc/error.c - Go-esque errors + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/error.h> + +const char *error_msg(error err) { + return (err._msg && err._msg[0]) + ? err._msg + : _errnum_str_msg(err.num); +} + +void error_cleanup(error *errptr) { + if (!errptr) + return; + if (errptr->_msg) + free(errptr->_msg); + errptr->num = E_NOERROR; + errptr->_msg = NULL; +} + +void fmt_print_error(lo_interface fmt_dest w, error err) { + fmt_print(w, (str, error_msg(err)), " (", _errnum_str_sym(err.num), ")"); +} diff --git a/libmisc/error_generated.c b/libmisc/error_generated.c new file mode 100644 index 0000000..e0afcab --- /dev/null +++ b/libmisc/error_generated.c @@ -0,0 +1,186 @@ +/* libmisc/error_generated.c - Generated by libmisc/error_generated.c.gen. DO NOT EDIT! */ + +#include <libmisc/error.h> + +const char *_errnum_str_sym(_errnum errnum) { + switch (errnum) { + case E_NOERROR: return "E_NOERROR"; + case E_EOF: return "E_EOF"; + case E_NET_EARP_TIMEOUT: return "E_NET_EARP_TIMEOUT"; + case E_NET_EACK_TIMEOUT: return "E_NET_EACK_TIMEOUT"; + case E_NET_ERECV_TIMEOUT: return "E_NET_ERECV_TIMEOUT"; + case E_NET_ECLOSED: return "E_NET_ECLOSED"; + case E_POSIX_E2BIG: return "E_POSIX_E2BIG"; + case E_POSIX_EACCES: return "E_POSIX_EACCES"; + case E_POSIX_EADDRINUSE: return "E_POSIX_EADDRINUSE"; + case E_POSIX_EADDRNOTAVAIL: return "E_POSIX_EADDRNOTAVAIL"; + case E_POSIX_EAFNOSUPPORT: return "E_POSIX_EAFNOSUPPORT"; + case E_POSIX_EAGAIN: return "E_POSIX_EAGAIN"; + case E_POSIX_EALREADY: return "E_POSIX_EALREADY"; + case E_POSIX_EBADF: return "E_POSIX_EBADF"; + case E_POSIX_EBADMSG: return "E_POSIX_EBADMSG"; + case E_POSIX_EBUSY: return "E_POSIX_EBUSY"; + case E_POSIX_ECANCELED: return "E_POSIX_ECANCELED"; + case E_POSIX_ECHILD: return "E_POSIX_ECHILD"; + case E_POSIX_ECONNABORTED: return "E_POSIX_ECONNABORTED"; + case E_POSIX_ECONNREFUSED: return "E_POSIX_ECONNREFUSED"; + case E_POSIX_ECONNRESET: return "E_POSIX_ECONNRESET"; + case E_POSIX_EDEADLK: return "E_POSIX_EDEADLK"; + case E_POSIX_EDESTADDRREQ: return "E_POSIX_EDESTADDRREQ"; + case E_POSIX_EDOM: return "E_POSIX_EDOM"; + case E_POSIX_EDQUOT: return "E_POSIX_EDQUOT"; + case E_POSIX_EEXIST: return "E_POSIX_EEXIST"; + case E_POSIX_EFAULT: return "E_POSIX_EFAULT"; + case E_POSIX_EFBIG: return "E_POSIX_EFBIG"; + case E_POSIX_EHOSTUNREACH: return "E_POSIX_EHOSTUNREACH"; + case E_POSIX_EIDRM: return "E_POSIX_EIDRM"; + case E_POSIX_EILSEQ: return "E_POSIX_EILSEQ"; + case E_POSIX_EINPROGRESS: return "E_POSIX_EINPROGRESS"; + case E_POSIX_EINTR: return "E_POSIX_EINTR"; + case E_POSIX_EINVAL: return "E_POSIX_EINVAL"; + case E_POSIX_EIO: return "E_POSIX_EIO"; + case E_POSIX_EISCONN: return "E_POSIX_EISCONN"; + case E_POSIX_EISDIR: return "E_POSIX_EISDIR"; + case E_POSIX_ELOOP: return "E_POSIX_ELOOP"; + case E_POSIX_EMFILE: return "E_POSIX_EMFILE"; + case E_POSIX_EMLINK: return "E_POSIX_EMLINK"; + case E_POSIX_EMSGSIZE: return "E_POSIX_EMSGSIZE"; + case E_POSIX_EMULTIHOP: return "E_POSIX_EMULTIHOP"; + case E_POSIX_ENAMETOOLONG: return "E_POSIX_ENAMETOOLONG"; + case E_POSIX_ENETDOWN: return "E_POSIX_ENETDOWN"; + case E_POSIX_ENETRESET: return "E_POSIX_ENETRESET"; + case E_POSIX_ENETUNREACH: return "E_POSIX_ENETUNREACH"; + case E_POSIX_ENFILE: return "E_POSIX_ENFILE"; + case E_POSIX_ENOBUFS: return "E_POSIX_ENOBUFS"; + case E_POSIX_ENODEV: return "E_POSIX_ENODEV"; + case E_POSIX_ENOENT: return "E_POSIX_ENOENT"; + case E_POSIX_ENOEXEC: return "E_POSIX_ENOEXEC"; + case E_POSIX_ENOLCK: return "E_POSIX_ENOLCK"; + case E_POSIX_ENOLINK: return "E_POSIX_ENOLINK"; + case E_POSIX_ENOMEM: return "E_POSIX_ENOMEM"; + case E_POSIX_ENOMSG: return "E_POSIX_ENOMSG"; + case E_POSIX_ENOPROTOOPT: return "E_POSIX_ENOPROTOOPT"; + case E_POSIX_ENOSPC: return "E_POSIX_ENOSPC"; + case E_POSIX_ENOSYS: return "E_POSIX_ENOSYS"; + case E_POSIX_ENOTCONN: return "E_POSIX_ENOTCONN"; + case E_POSIX_ENOTDIR: return "E_POSIX_ENOTDIR"; + case E_POSIX_ENOTEMPTY: return "E_POSIX_ENOTEMPTY"; + case E_POSIX_ENOTRECOVERABLE: return "E_POSIX_ENOTRECOVERABLE"; + case E_POSIX_ENOTSOCK: return "E_POSIX_ENOTSOCK"; + case E_POSIX_ENOTSUP: return "E_POSIX_ENOTSUP"; + case E_POSIX_ENOTTY: return "E_POSIX_ENOTTY"; + case E_POSIX_ENXIO: return "E_POSIX_ENXIO"; + case E_POSIX_EOPNOTSUPP: return "E_POSIX_EOPNOTSUPP"; + case E_POSIX_EOVERFLOW: return "E_POSIX_EOVERFLOW"; + case E_POSIX_EOWNERDEAD: return "E_POSIX_EOWNERDEAD"; + case E_POSIX_EPERM: return "E_POSIX_EPERM"; + case E_POSIX_EPIPE: return "E_POSIX_EPIPE"; + case E_POSIX_EPROTO: return "E_POSIX_EPROTO"; + case E_POSIX_EPROTONOSUPPORT: return "E_POSIX_EPROTONOSUPPORT"; + case E_POSIX_EPROTOTYPE: return "E_POSIX_EPROTOTYPE"; + case E_POSIX_ERANGE: return "E_POSIX_ERANGE"; + case E_POSIX_EROFS: return "E_POSIX_EROFS"; + case E_POSIX_ESOCKTNOSUPPORT: return "E_POSIX_ESOCKTNOSUPPORT"; + case E_POSIX_ESPIPE: return "E_POSIX_ESPIPE"; + case E_POSIX_ESRCH: return "E_POSIX_ESRCH"; + case E_POSIX_ESTALE: return "E_POSIX_ESTALE"; + case E_POSIX_ETIMEDOUT: return "E_POSIX_ETIMEDOUT"; + case E_POSIX_ETXTBSY: return "E_POSIX_ETXTBSY"; + case E_POSIX_EWOULDBLOCK: return "E_POSIX_EWOULDBLOCK"; + case E_POSIX_EXDEV: return "E_POSIX_EXDEV"; + case E_EUNKNOWN: return "E_EUNKNOWN"; + default: return "E_<invalid>"; + } +} + +const char *_errnum_str_msg(_errnum errnum) { + switch (errnum) { + case E_NOERROR: return "no error"; + case E_EOF: return "EOF"; + case E_NET_EARP_TIMEOUT: return "ARP timeout"; + case E_NET_EACK_TIMEOUT: return "TCP ACK timeout"; + case E_NET_ERECV_TIMEOUT: return "receive timeout"; + case E_NET_ECLOSED: return "already closed"; + case E_POSIX_E2BIG: return "Argument list too long"; + case E_POSIX_EACCES: return "Permission denied"; + case E_POSIX_EADDRINUSE: return "Address in use"; + case E_POSIX_EADDRNOTAVAIL: return "Address not available"; + case E_POSIX_EAFNOSUPPORT: return "Address family not supported"; + case E_POSIX_EAGAIN: return "Resource unavailable, try again"; + case E_POSIX_EALREADY: return "Connection already in progress"; + case E_POSIX_EBADF: return "Bad file descriptor"; + case E_POSIX_EBADMSG: return "Bad message"; + case E_POSIX_EBUSY: return "Device or resource busy"; + case E_POSIX_ECANCELED: return "Operation canceled"; + case E_POSIX_ECHILD: return "No child processes"; + case E_POSIX_ECONNABORTED: return "Connection aborted"; + case E_POSIX_ECONNREFUSED: return "Connection refused"; + case E_POSIX_ECONNRESET: return "Connection reset"; + case E_POSIX_EDEADLK: return "Resource deadlock would occur"; + case E_POSIX_EDESTADDRREQ: return "Destination address required"; + case E_POSIX_EDOM: return "Mathematics argument out of domain of function"; + case E_POSIX_EDQUOT: return "Reserved"; + case E_POSIX_EEXIST: return "File exists"; + case E_POSIX_EFAULT: return "Bad address"; + case E_POSIX_EFBIG: return "File too large"; + case E_POSIX_EHOSTUNREACH: return "Host is unreachable"; + case E_POSIX_EIDRM: return "Identifier removed"; + case E_POSIX_EILSEQ: return "Illegal byte sequence"; + case E_POSIX_EINPROGRESS: return "Operation in progress"; + case E_POSIX_EINTR: return "Interrupted function"; + case E_POSIX_EINVAL: return "Invalid argument"; + case E_POSIX_EIO: return "I/O error"; + case E_POSIX_EISCONN: return "Socket is connected"; + case E_POSIX_EISDIR: return "Is a directory"; + case E_POSIX_ELOOP: return "Too many levels of symbolic links"; + case E_POSIX_EMFILE: return "File descriptor value too large"; + case E_POSIX_EMLINK: return "Too many hard links"; + case E_POSIX_EMSGSIZE: return "Message too large"; + case E_POSIX_EMULTIHOP: return "Reserved"; + case E_POSIX_ENAMETOOLONG: return "Filename too long"; + case E_POSIX_ENETDOWN: return "Network is down"; + case E_POSIX_ENETRESET: return "Connection aborted by network"; + case E_POSIX_ENETUNREACH: return "Network unreachable"; + case E_POSIX_ENFILE: return "Too many files open in system"; + case E_POSIX_ENOBUFS: return "No buffer space available"; + case E_POSIX_ENODEV: return "No such device"; + case E_POSIX_ENOENT: return "No such file or directory"; + case E_POSIX_ENOEXEC: return "Executable file format error"; + case E_POSIX_ENOLCK: return "No locks available"; + case E_POSIX_ENOLINK: return "Reserved"; + case E_POSIX_ENOMEM: return "Not enough space"; + case E_POSIX_ENOMSG: return "No message of the desired type"; + case E_POSIX_ENOPROTOOPT: return "Protocol not available"; + case E_POSIX_ENOSPC: return "No space left on device"; + case E_POSIX_ENOSYS: return "Functionality not supported"; + case E_POSIX_ENOTCONN: return "The socket is not connected"; + case E_POSIX_ENOTDIR: return "Not a directory or a symbolic link to a directory"; + case E_POSIX_ENOTEMPTY: return "Directory not empty"; + case E_POSIX_ENOTRECOVERABLE: return "State not recoverable"; + case E_POSIX_ENOTSOCK: return "Not a socket"; + case E_POSIX_ENOTSUP: return "Not supported"; + case E_POSIX_ENOTTY: return "Inappropriate I/O control operation"; + case E_POSIX_ENXIO: return "No such device or address"; + case E_POSIX_EOPNOTSUPP: return "Operation not supported on socket"; + case E_POSIX_EOVERFLOW: return "Value too large to be stored in data type"; + case E_POSIX_EOWNERDEAD: return "Previous owner died"; + case E_POSIX_EPERM: return "Operation not permitted"; + case E_POSIX_EPIPE: return "Broken pipe"; + case E_POSIX_EPROTO: return "Protocol error"; + case E_POSIX_EPROTONOSUPPORT: return "Protocol not supported"; + case E_POSIX_EPROTOTYPE: return "Protocol wrong type for socket"; + case E_POSIX_ERANGE: return "Result too large"; + case E_POSIX_EROFS: return "Read-only file system"; + case E_POSIX_ESOCKTNOSUPPORT: return "Socket type not supported"; + case E_POSIX_ESPIPE: return "Invalid seek"; + case E_POSIX_ESRCH: return "No such process"; + case E_POSIX_ESTALE: return "Reserved"; + case E_POSIX_ETIMEDOUT: return "Connection timed out"; + case E_POSIX_ETXTBSY: return "Text file busy"; + case E_POSIX_EWOULDBLOCK: return "Operation would block"; + case E_POSIX_EXDEV: return "Improper hard link"; + case E_EUNKNOWN: + default: + return "unknown error"; + } +} diff --git a/libmisc/error_generated.c.gen b/libmisc/error_generated.c.gen new file mode 100755 index 0000000..944d909 --- /dev/null +++ b/libmisc/error_generated.c.gen @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# libmisc/error_generated.c.gen - Generate _errnum strings +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +error_h=$1 +outfile=$2 + +{ + echo "/* ${outfile} - Generated by $0. DO NOT EDIT! */" + echo + echo '#include <libmisc/error.h>' + echo + echo 'const char *_errnum_str_sym(_errnum errnum) {' + echo $'\tswitch (errnum) {' + sed -nE \ + -e 's@^(#define)?\s+(E_[_A-Z0-9]+)[ ,][^/]*/\* ([^*(]+) (\*/|\().*@'$'\tcase \\2: return "\\2";''@p' \ + -- "$error_h" + echo $'\tcase E_EUNKNOWN: return "E_EUNKNOWN";' + echo $'\tdefault: return "E_<invalid>";' + echo $'\t}' + echo '}' + echo + echo 'const char *_errnum_str_msg(_errnum errnum) {' + echo $'\tswitch (errnum) {' + sed -nE \ + -e 's@^(#define)?\s+(E_[_A-Z0-9]+)[ ,][^/]*/\* ([^*(]+) (\*/|\().*@'$'\tcase \\2: return "\\3";''@p' \ + -- "$error_h" + echo $'\tcase E_EUNKNOWN:' + echo $'\tdefault:' + echo $'\t\treturn "unknown error";' + echo $'\t}' + echo '}' +} >"$outfile" diff --git a/libmisc/fmt.c b/libmisc/fmt.c new file mode 100644 index 0000000..7c18ef5 --- /dev/null +++ b/libmisc/fmt.c @@ -0,0 +1,266 @@ +/* libmisc/fmt.c - Write formatted text + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for strnlen() */ + +#include <libmisc/utf8.h> + +#include <libmisc/fmt.h> + +static const char *const hexdig = "0123456789ABCDEF"; + +/* small/trivial formatters ***************************************************/ + +void fmt_print_mem(lo_interface fmt_dest w, const void *_str, size_t size) { + const uint8_t *str = _str; + while (size--) + fmt_print_byte(w, *(str++)); +} +void fmt_print_str(lo_interface fmt_dest w, const char *str) { + while (*str) + fmt_print_byte(w, *(str++)); +} +void fmt_print_strn(lo_interface fmt_dest w, const char *str, size_t size) { + while (size-- && *str) + fmt_print_byte(w, *(str++)); +} + +void fmt_print_hmem(lo_interface fmt_dest w, const void *_str, size_t size) { + const uint8_t *str = _str; + fmt_print_byte(w, '{'); + for (size_t i = 0; i < size; i++) { + if (i) + fmt_print_byte(w, ','); + fmt_print_hbyte(w, str[i]); + } + fmt_print_byte(w, '}'); +} + +void fmt_print_byte(lo_interface fmt_dest w, uint8_t b) { + LO_CALL(w, putb, b); +} + +void fmt_print_bool(lo_interface fmt_dest w, bool b) { + fmt_print_str(w, b ? "true" : "false"); +} + +void fmt_print_base16_u8_(lo_interface fmt_dest w, uint8_t x) { + fmt_print(w, "0x", (rjust, 2, '0', (base16, x))); +} +void fmt_print_base16_u16_(lo_interface fmt_dest w, uint16_t x) { + fmt_print(w, "0x", (rjust, 4, '0', (base16, x))); +} +void fmt_print_base16_u32_(lo_interface fmt_dest w, uint32_t x) { + fmt_print(w, "0x", (rjust, 8, '0', (base16, x))); +} +void fmt_print_base16_u64_(lo_interface fmt_dest w, uint64_t x) { + fmt_print(w, "0x", (rjust, 16, '0', (base16, x))); +} + +void fmt_print_ptr(lo_interface fmt_dest w, void *ptr) { + LM_CAT3_(fmt_print_base16_u, __INTPTR_WIDTH__, _)(w, (uintptr_t)ptr); +} + +/* quote **********************************************************************/ + +/** + * Quote a byte to ASCII-only C syntax. + */ +void fmt_print_qbyte(lo_interface fmt_dest w, uint8_t b) { + fmt_print_byte(w, '\''); + if (b == '\0' || + b == '\b' || + b == '\f' || + b == '\n' || + b == '\r' || + b == '\t' || + b == '\v' || + b == '\\' || + b == '\'' || + b == '"' || + b == '?') { + fmt_print_byte(w, '\\'); + switch (b) { + case '\0': fmt_print_byte(w, '0'); break; + case '\a': fmt_print_byte(w, 'a'); break; + case '\b': fmt_print_byte(w, 'b'); break; + case '\f': fmt_print_byte(w, 'f'); break; + case '\n': fmt_print_byte(w, 'n'); break; + case '\r': fmt_print_byte(w, 'r'); break; + case '\t': fmt_print_byte(w, 't'); break; + case '\v': fmt_print_byte(w, 'v'); break; + case '\\': fmt_print_byte(w, '\\'); break; + case '\'': fmt_print_byte(w, '\''); break; + case '"': fmt_print_byte(w, '"'); break; + case '?': fmt_print_byte(w, '?'); break; + } + } else if (' ' <= b && b <= '~') { + fmt_print_byte(w, b); + } else { + fmt_print_byte(w, '\\'); + fmt_print_byte(w, 'x'); + fmt_print_byte(w, hexdig[(b >> 4) & 0xF]); + fmt_print_byte(w, hexdig[(b >> 0) & 0xF]); + } + fmt_print_byte(w, '\''); +} + +/** + * Quote a region of memory to ASCII-only C string syntax. Valid + * UTF-8 is quoted as short C-escape characters, \uABCD, or + * \UABCDABCD; invalid UTF-8 is quoted as \xAB. + */ +void fmt_print_qmem(lo_interface fmt_dest w, const void *_str, size_t size) { + const uint8_t *str = _str; + fmt_print_byte(w, '"'); + for (size_t pos = 0; pos < size;) { + uint32_t ch; + uint8_t chlen; + utf8_decode_codepoint(&str[pos], size-pos, &ch, &chlen); + if (!chlen) { + /* invalid UTF-8 */ + /* \xAB */ + fmt_print_byte(w, '\\'); + fmt_print_byte(w, 'x'); + fmt_print_byte(w, hexdig[(str[pos] >> 4) & 0xF]); + fmt_print_byte(w, hexdig[(str[pos] >> 0) & 0xF]); + pos++; + continue; + } + if (ch == '\0' || + ch == '\b' || + ch == '\f' || + ch == '\n' || + ch == '\r' || + ch == '\t' || + ch == '\v' || + ch == '\\' || + ch == '\'' || + ch == '"' || + ch == '?') { + /* short C-escape */ + fmt_print_byte(w, '\\'); + switch (ch) { + case '\0': fmt_print_byte(w, '0'); break; + case '\a': fmt_print_byte(w, 'a'); break; + case '\b': fmt_print_byte(w, 'b'); break; + case '\f': fmt_print_byte(w, 'f'); break; + case '\n': fmt_print_byte(w, 'n'); break; + case '\r': fmt_print_byte(w, 'r'); break; + case '\t': fmt_print_byte(w, 't'); break; + case '\v': fmt_print_byte(w, 'v'); break; + case '\\': fmt_print_byte(w, '\\'); break; + case '\'': fmt_print_byte(w, '\''); break; + case '"': fmt_print_byte(w, '"'); break; + case '?': fmt_print_byte(w, '?'); break; + } + } else if (' ' <= ch && ch <= '~') { + /* no escaping */ + fmt_print_byte(w, ch); + } else if (ch < 0x10000) { + /* \uABCD */ + fmt_print_byte(w, '\\'); + fmt_print_byte(w, 'u'); + fmt_print_byte(w, hexdig[(ch >> 12) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 8) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 4) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 0) & 0xF]); + } else { + /* \UABCDABCD */ + fmt_print_byte(w, '\\'); + fmt_print_byte(w, 'U'); + fmt_print_byte(w, hexdig[(ch >> 28) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 24) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 20) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 16) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 12) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 8) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 4) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 0) & 0xF]); + } + pos += chlen; + } + fmt_print_byte(w, '"'); +} + +void fmt_print_qstr(lo_interface fmt_dest w, const char *str) { + fmt_print_qmem(w, str, strlen(str)); +} + +void fmt_print_qstrn(lo_interface fmt_dest w, const char *str, size_t n) { + fmt_print_qmem(w, str, strnlen(str, n)); +} + +/* int ************************************************************************/ + +#define declare(BASE, BITS) \ + void _fmt_print_base##BASE##_s##BITS(lo_interface fmt_dest w, \ + int##BITS##_t val) { \ + if (val < 0) { \ + fmt_print_byte(w, '-'); \ + val = -val; \ + } \ + _fmt_print_base##BASE##_u##BITS(w, (uint##BITS##_t)val); \ + } \ + \ + void _fmt_print_base##BASE##_u##BITS(lo_interface fmt_dest w, \ + uint##BITS##_t absval) { \ + /* This digit-counting is O(log(absval)); there are \ + * `__builtin_clz`-based O(1) ways to do this, but when I \ + * tried them they bloated the code-size too much. And this \ + * function as a whole is already O(log(absval)) anyway \ + * because of actually printing the digits. */ \ + unsigned ndigits = 1; \ + uint##BITS##_t div = 1; \ + while (absval / div >= BASE) { \ + div *= BASE; \ + ndigits++; \ + } \ + \ + for (unsigned i = 0; i < ndigits; i++) { \ + unsigned digit = (unsigned) (absval / div); \ + absval %= div; \ + div /= BASE; \ + fmt_print_byte(w, hexdig[digit]); \ + } \ + } \ + LM_FORCE_SEMICOLON + +declare(2, 8); +declare(2, 16); +declare(2, 32); +declare(2, 64); + +declare(8, 8); +declare(8, 16); +declare(8, 32); +declare(8, 64); + +declare(10, 8); +declare(10, 16); +declare(10, 32); +declare(10, 64); + +declare(16, 8); +declare(16, 16); +declare(16, 32); +declare(16, 64); + +#undef declare + +/* fmt_buf ********************************************************************/ + +LO_IMPLEMENTATION_C(fmt_dest, struct fmt_buf, fmt_buf, static); + +static void fmt_buf_putb(struct fmt_buf *buf, uint8_t b) { + if (buf->len < buf->cap) + ((uint8_t *)(buf->dat))[buf->len] = b; + buf->len++; +} + +static size_t fmt_buf_tell(struct fmt_buf *buf) { + return buf->len; +} diff --git a/libmisc/hash.c b/libmisc/hash.c new file mode 100644 index 0000000..3814cec --- /dev/null +++ b/libmisc/hash.c @@ -0,0 +1,24 @@ +/* libmisc/hash.c - General-purpose hash utilities + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/hash.h> + +/* djb2 hash */ +void hash_init(hash_t *hash) { + *hash = 5381; +} +void hash_write(hash_t *hash, void *dat, size_t len) { + for (size_t i = 0; i < len; i++) + *hash = (*hash * 33) + (hash_t)(((unsigned char *)dat)[i]); +} + +/* utilities */ +hash_t hash(void *dat, size_t len) { + hash_t h; + hash_init(&h); + hash_write(&h, dat, len); + return h; +} diff --git a/libmisc/include/libmisc/_intercept.h b/libmisc/include/libmisc/_intercept.h new file mode 100644 index 0000000..fa327d6 --- /dev/null +++ b/libmisc/include/libmisc/_intercept.h @@ -0,0 +1,24 @@ +/* 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 defines abort() to be [[gnu::weak]] already, but + * depending on optimization options glibc might not, and GCC might + * assume it knows what it does and optimize it out. So define our + * own `__lm_` wrapper that GCC/glibc won't interfere with. + */ + +[[noreturn]] void __lm_abort(void); + +/* While newlib defines putchar() to be [[gnu::weak]], pico_stdio does + * not. Plus the above about optimizations. + */ + +void __lm_putchar(unsigned char c); + +#endif /* _LIBMISC__INTERCEPT_H_ */ diff --git a/libmisc/include/libmisc/alloc.h b/libmisc/include/libmisc/alloc.h new file mode 100644 index 0000000..afddbce --- /dev/null +++ b/libmisc/include/libmisc/alloc.h @@ -0,0 +1,26 @@ +/* libmisc/alloc.h - Type-safe wrappers around alloca and malloc + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_ALLOC_H_ +#define _LIBMISC_ALLOC_H_ + +#include <alloca.h> /* for alloca() */ +#include <stdlib.h> /* for calloc(), free() */ +#include <string.h> /* for memset() */ + +#define stack_alloc(N, TYP) ({ \ + size_t _size; \ + TYP *_ret = NULL; \ + if (!__builtin_mul_overflow(N, sizeof(TYP), &_size)) { \ + _ret = alloca(_size); \ + memset(_ret, 0, _size); \ + } \ + _ret; \ +}) + +#define heap_alloc(N, TYP) ((TYP *)calloc(N, sizeof(TYP))) + +#endif /* _LIBMISC_ALLOC_H_ */ diff --git a/libmisc/include/libmisc/assert.h b/libmisc/include/libmisc/assert.h index da2ba2b..ccdb288 100644 --- a/libmisc/include/libmisc/assert.h +++ b/libmisc/include/libmisc/assert.h @@ -1,6 +1,6 @@ /* libmisc/assert.h - More assertions * - * 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 */ @@ -10,15 +10,20 @@ #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) +# 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, NULL) /* C89, POSIX-2001 */ -#define assert_notreached(msg) do { __assert_msg(0, "notreached", msg); __builtin_unreachable(); } while (0) /* libmisc */ -#define static_assert _Static_assert /* C11 */ +#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 */ +#define static_assert_as_expr(...) (sizeof(struct {static_assert(__VA_ARGS__);})) /* libmisc */ #endif /* _LIBMISC_ASSERT_H_ */ diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h index b1bc55c..966c3bc 100644 --- a/libmisc/include/libmisc/endian.h +++ b/libmisc/include/libmisc/endian.h @@ -1,134 +1,34 @@ /* 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 <stddef.h> /* for size_t */ #include <stdint.h> /* for uint{n}_t */ -#include <libmisc/assert.h> - -/* Big endian *****************************************************************/ - -typedef struct { - uint8_t octets[2]; -} uint16be_t; -static_assert(sizeof(uint16be_t) == 2); - -static inline size_t uint16be_encode(uint8_t *out, uint16_t in) { - out[0] = (uint8_t)((in >> 8) & 0xFF); - out[1] = (uint8_t)((in >> 0) & 0xFF); - return 2; -} - -static inline uint16_t uint16be_decode(uint8_t *in) { - return (((uint16_t)(in[0])) << 8) - | (((uint16_t)(in[1])) << 0) - ; -} - -static inline uint16be_t uint16be_marshal(uint16_t in) { - uint16be_t out; - uint16be_encode(out.octets, in); - return out; -} - -static inline uint16_t uint16be_unmarshal(uint16be_t in) { - return uint16be_decode(in.octets); -} - -typedef struct { - uint8_t octets[4]; -} uint32be_t; -static_assert(sizeof(uint32be_t) == 4); - -static inline size_t uint32be_encode(uint8_t *out, uint32_t in) { - out[0] = (uint8_t)((in >> 24) & 0xFF); - out[1] = (uint8_t)((in >> 16) & 0xFF); - out[2] = (uint8_t)((in >> 8) & 0xFF); - out[3] = (uint8_t)((in >> 0) & 0xFF); - return 4; -} - -static inline uint32_t uint32be_decode(uint8_t *in) { - return (((uint32_t)(in[0])) << 24) - | (((uint32_t)(in[1])) << 16) - | (((uint32_t)(in[2])) << 8) - | (((uint32_t)(in[3])) << 0) - ; -} - -static inline uint32be_t uint32be_marshal(uint32_t in) { - uint32be_t out; - uint32be_encode(out.octets, in); - return out; -} - -static inline uint32_t uint32be_unmarshal(uint32be_t in) { - return uint32be_decode(in.octets); -} - -/* Little endian **************************************************************/ - -typedef struct { - uint8_t octets[2]; -} uint16le_t; -static_assert(sizeof(uint16le_t) == 2); - -static inline size_t uint16le_encode(uint8_t *out, uint16_t in) { - out[0] = (uint8_t)((in >> 0) & 0xFF); - out[1] = (uint8_t)((in >> 8) & 0xFF); - return 2; -} - -static inline uint16_t uint16le_decode(uint8_t *in) { - return (((uint16_t)(in[0])) << 0) - | (((uint16_t)(in[1])) << 8) - ; -} - -static inline uint16le_t uint16le_marshal(uint16_t in) { - uint16le_t out; - uint16le_encode(out.octets, in); - return out; -} - -static inline uint16_t uint16le_unmarshal(uint16le_t in) { - return uint16le_decode(in.octets); -} - -typedef struct { - uint8_t octets[4]; -} uint32le_t; -static_assert(sizeof(uint32le_t) == 4); - -static inline size_t uint32le_encode(uint8_t *out, uint32_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); - return 4; -} - -static inline uint32_t uint32le_decode(uint8_t *in) { - return (((uint32_t)(in[0])) << 0) - | (((uint32_t)(in[1])) << 8) - | (((uint32_t)(in[2])) << 16) - | (((uint32_t)(in[3])) << 24) - ; -} - -static inline uint32le_t uint32le_marshal(uint32_t in) { - uint32le_t out; - uint32le_encode(out.octets, in); - return out; -} - -static inline uint32_t uint32le_unmarshal(uint32le_t in) { - return uint32le_decode(in.octets); -} +#define _endian_declare_conv(NBIT, ENDIAN) \ + /* byte array encode/decode */ \ + size_t uint##NBIT##ENDIAN##_encode(uint8_t *out, uint##NBIT##_t in); \ + uint##NBIT##_t uint##NBIT##ENDIAN##_decode(uint8_t *in); \ + /* struct marshal/unmarshal */ \ + typedef struct { \ + uint8_t octets[NBIT/8]; \ + } uint##NBIT##ENDIAN##_t; \ + uint##NBIT##ENDIAN##_t uint##NBIT##ENDIAN##_marshal(uint##NBIT##_t in); \ + uint##NBIT##_t uint##NBIT##ENDIAN##_unmarshal(uint##NBIT##ENDIAN##_t in) + +_endian_declare_conv(16, be); +_endian_declare_conv(32, be); +_endian_declare_conv(64, be); + +_endian_declare_conv(16, le); +_endian_declare_conv(32, le); +_endian_declare_conv(64, le); + +#undef _endian_declare_conv #endif /* _LIBMISC_ENDIAN_H_ */ diff --git a/libmisc/include/libmisc/error.h b/libmisc/include/libmisc/error.h new file mode 100644 index 0000000..4110626 --- /dev/null +++ b/libmisc/include/libmisc/error.h @@ -0,0 +1,167 @@ +/* libmisc/error.h - Go-esque errors + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_ERROR_H_ +#define _LIBMISC_ERROR_H_ + +#include <stddef.h> /* for size_t */ +#include <stdint.h> /* for uint{n}_t */ + +#include <libmisc/assert.h> +#include <libmisc/fmt.h> + +/* _errnum ********************************************************************/ + +typedef enum { + /* Original to libmisc */ + E_NOERROR = 0, /* no error */ + E_EOF, /* EOF */ + E_NET_EARP_TIMEOUT, /* ARP timeout */ + E_NET_EACK_TIMEOUT, /* TCP ACK timeout */ + E_NET_ERECV_TIMEOUT, /* receive timeout */ + E_NET_ECLOSED, /* already closed */ + /* At least C99 through C23 */ + E_STDC_EDOM, + E_STDC_EILSEQ, + E_STDC_ERANGE, + /* POSIX-2024 */ + E_POSIX_E2BIG, /* Argument list too long */ + E_POSIX_EACCES, /* Permission denied */ + E_POSIX_EADDRINUSE, /* Address in use */ + E_POSIX_EADDRNOTAVAIL, /* Address not available */ + E_POSIX_EAFNOSUPPORT, /* Address family not supported */ + E_POSIX_EAGAIN, /* Resource unavailable, try again (may be the same value as [EWOULDBLOCK]) */ + E_POSIX_EALREADY, /* Connection already in progress */ + E_POSIX_EBADF, /* Bad file descriptor */ + E_POSIX_EBADMSG, /* Bad message */ + E_POSIX_EBUSY, /* Device or resource busy */ + E_POSIX_ECANCELED, /* Operation canceled */ + E_POSIX_ECHILD, /* No child processes */ + E_POSIX_ECONNABORTED, /* Connection aborted */ + E_POSIX_ECONNREFUSED, /* Connection refused */ + E_POSIX_ECONNRESET, /* Connection reset */ + E_POSIX_EDEADLK, /* Resource deadlock would occur */ + E_POSIX_EDESTADDRREQ, /* Destination address required */ +#define E_POSIX_EDOM E_STDC_EDOM /* Mathematics argument out of domain of function */ + E_POSIX_EDQUOT, /* Reserved */ + E_POSIX_EEXIST, /* File exists */ + E_POSIX_EFAULT, /* Bad address */ + E_POSIX_EFBIG, /* File too large */ + E_POSIX_EHOSTUNREACH, /* Host is unreachable */ + E_POSIX_EIDRM, /* Identifier removed */ +#define E_POSIX_EILSEQ E_STDC_EILSEQ /* Illegal byte sequence */ /* (in the context of text encoding) */ + E_POSIX_EINPROGRESS, /* Operation in progress */ + E_POSIX_EINTR, /* Interrupted function */ + E_POSIX_EINVAL, /* Invalid argument */ + E_POSIX_EIO, /* I/O error */ + E_POSIX_EISCONN, /* Socket is connected */ + E_POSIX_EISDIR, /* Is a directory */ + E_POSIX_ELOOP, /* Too many levels of symbolic links */ + E_POSIX_EMFILE, /* File descriptor value too large */ + E_POSIX_EMLINK, /* Too many hard links */ + E_POSIX_EMSGSIZE, /* Message too large */ + E_POSIX_EMULTIHOP, /* Reserved */ + E_POSIX_ENAMETOOLONG, /* Filename too long */ + E_POSIX_ENETDOWN, /* Network is down */ + E_POSIX_ENETRESET, /* Connection aborted by network */ + E_POSIX_ENETUNREACH, /* Network unreachable */ + E_POSIX_ENFILE, /* Too many files open in system */ + E_POSIX_ENOBUFS, /* No buffer space available */ + E_POSIX_ENODEV, /* No such device */ + E_POSIX_ENOENT, /* No such file or directory */ + E_POSIX_ENOEXEC, /* Executable file format error */ + E_POSIX_ENOLCK, /* No locks available */ + E_POSIX_ENOLINK, /* Reserved */ + E_POSIX_ENOMEM, /* Not enough space */ + E_POSIX_ENOMSG, /* No message of the desired type */ + E_POSIX_ENOPROTOOPT, /* Protocol not available */ + E_POSIX_ENOSPC, /* No space left on device */ + E_POSIX_ENOSYS, /* Functionality not supported */ + E_POSIX_ENOTCONN, /* The socket is not connected */ + E_POSIX_ENOTDIR, /* Not a directory or a symbolic link to a directory */ + E_POSIX_ENOTEMPTY, /* Directory not empty */ + E_POSIX_ENOTRECOVERABLE, /* State not recoverable */ /* Added in POSIX-2008 */ + E_POSIX_ENOTSOCK, /* Not a socket */ + E_POSIX_ENOTSUP, /* Not supported (may be the same value as [EOPNOTSUPP]) */ + E_POSIX_ENOTTY, /* Inappropriate I/O control operation */ + E_POSIX_ENXIO, /* No such device or address */ + E_POSIX_EOPNOTSUPP, /* Operation not supported on socket (may be the same value as [ENOTSUP]) */ + E_POSIX_EOVERFLOW, /* Value too large to be stored in data type */ + E_POSIX_EOWNERDEAD, /* Previous owner died */ /* Added in POSIX-2008 */ + E_POSIX_EPERM, /* Operation not permitted */ + E_POSIX_EPIPE, /* Broken pipe */ + E_POSIX_EPROTO, /* Protocol error */ + E_POSIX_EPROTONOSUPPORT, /* Protocol not supported */ + E_POSIX_EPROTOTYPE, /* Protocol wrong type for socket */ +#define E_POSIX_ERANGE E_STDC_ERANGE /* Result too large */ + E_POSIX_EROFS, /* Read-only file system */ + E_POSIX_ESOCKTNOSUPPORT, /* Socket type not supported */ + E_POSIX_ESPIPE, /* Invalid seek */ + E_POSIX_ESRCH, /* No such process */ + E_POSIX_ESTALE, /* Reserved */ + E_POSIX_ETIMEDOUT, /* Connection timed out */ + E_POSIX_ETXTBSY, /* Text file busy */ + E_POSIX_EWOULDBLOCK, /* Operation would block (may be the same value as [EAGAIN]) */ + E_POSIX_EXDEV, /* Improper hard link */ + /* End cap */ + E_EUNKNOWN, +} _errnum; + +const char *_errnum_str_sym(_errnum); +const char *_errnum_str_msg(_errnum); + +/* error **********************************************************************/ + +typedef struct { + _errnum num; + char *_msg; +} error; + +#ifdef NDEBUG +#define error_new(ERRNUM, ...) ((error){ \ + .num = ERRNUM , \ + __VA_OPT__(._msg = fmt_asprint(__VA_ARGS__),) \ +}) +#else +#define error_new(ERRNUM, ...) ((error){ \ + .num = ERRNUM, \ + ._msg = fmt_asprint("" __VA_OPT__(,) __VA_ARGS__), \ +}) +#endif + +#define ERROR_NULL ((error){}) +#define ERROR_IS_NULL(err) ((err).num == 0 && (err)._msg == NULL) + +const char *error_msg(error err); +void error_cleanup(error *errptr); +void fmt_print_error(lo_interface fmt_dest w, error err); + +/* or_error ******************************************************************/ + +#define DECLARE_ERROR_OR(TYP) typedef struct { union { TYP TYP; error err; }; bool is_err; } TYP##_or_error +#define ERROR_NEW_VAL(TYP, VAL) ((TYP##_or_error){ .TYP = (VAL), .is_err = false }) +#define ERROR_NEW_ERR(TYP, ERR) ((TYP##_or_error){ .err = (ERR), .is_err = true }) + +/* and_error *****************************************************************/ + +#define DECLARE_ERROR_AND(TYP) typedef struct { TYP TYP; error err; } TYP##_and_error +#define ERROR_AND(TYP, VAL, ERR) ((TYP##_and_error){ .TYP = (VAL), .err = (ERR) }) + +/* some pre-defined types ****************************************************/ + +DECLARE_ERROR_OR(size_t); +DECLARE_ERROR_OR(uint8_t); +DECLARE_ERROR_OR(uint16_t); +DECLARE_ERROR_OR(uint32_t); +DECLARE_ERROR_OR(uint64_t); + +DECLARE_ERROR_AND(size_t); +DECLARE_ERROR_AND(uint8_t); +DECLARE_ERROR_AND(uint16_t); +DECLARE_ERROR_AND(uint32_t); +DECLARE_ERROR_AND(uint64_t); + +#endif /* _LIBMISC_ERROR_H_ */ diff --git a/libmisc/include/libmisc/fmt.h b/libmisc/include/libmisc/fmt.h new file mode 100644 index 0000000..135e48b --- /dev/null +++ b/libmisc/include/libmisc/fmt.h @@ -0,0 +1,163 @@ +/* libmisc/fmt.h - Write formatted text + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_FMT_H_ +#define _LIBMISC_FMT_H_ + +#include <stddef.h> /* for size_t */ +#include <stdint.h> /* for (u)int{n}_t */ +#include <stdlib.h> /* for realloc() */ + +#include <libmisc/macro.h> +#include <libmisc/obj.h> + +/* destination interface ******************************************************/ + +#define fmt_dest_LO_IFACE \ + LO_FUNC(void , putb, uint8_t b) \ + LO_FUNC(size_t, tell) +LO_INTERFACE(fmt_dest); + +/* type-specific fmt_print_() functions ***************************************/ + +/* Simple bytes. */ +void fmt_print_byte(lo_interface fmt_dest w, uint8_t b); +void fmt_print_mem(lo_interface fmt_dest w, const void *str, size_t size); +void fmt_print_str(lo_interface fmt_dest w, const char *str); +void fmt_print_strn(lo_interface fmt_dest w, const char *str, size_t size); + +/* Quoted bytes. */ +void fmt_print_qbyte(lo_interface fmt_dest w, uint8_t b); +void fmt_print_qmem(lo_interface fmt_dest w, const void *str, size_t size); +void fmt_print_qstr(lo_interface fmt_dest w, const char *str); +void fmt_print_qstrn(lo_interface fmt_dest w, const char *str, size_t size); + +/* Hex bytes. */ +#define fmt_print_hbyte fmt_print_base16_u8_ +void fmt_print_hmem(lo_interface fmt_dest w, const void *str, size_t size); + +/* Integers. */ +#define _fmt_declare_base(base) \ + void _fmt_print_base##base##_u8(lo_interface fmt_dest w, uint8_t val); \ + void _fmt_print_base##base##_u16(lo_interface fmt_dest w, uint16_t val); \ + void _fmt_print_base##base##_u32(lo_interface fmt_dest w, uint32_t val); \ + void _fmt_print_base##base##_u64(lo_interface fmt_dest w, uint64_t val); \ + void _fmt_print_base##base##_s8(lo_interface fmt_dest w, int8_t val); \ + void _fmt_print_base##base##_s16(lo_interface fmt_dest w, int16_t val); \ + void _fmt_print_base##base##_s32(lo_interface fmt_dest w, int32_t val); \ + void _fmt_print_base##base##_s64(lo_interface fmt_dest w, int64_t val); \ + LM_FORCE_SEMICOLON +_fmt_declare_base(2); +_fmt_declare_base(8); +_fmt_declare_base(10); +_fmt_declare_base(16); +#undef _fmt_declare_base + +#define _fmt_intswitch(val, prefix) _Generic((val) , \ + unsigned char : LM_CAT3_(prefix, u, __SCHAR_WIDTH__) , \ + unsigned short : LM_CAT3_(prefix, u, __SHRT_WIDTH__) , \ + unsigned int : LM_CAT3_(prefix, u, __INT_WIDTH__) , \ + unsigned long : LM_CAT3_(prefix, u, __LONG_WIDTH__) , \ + unsigned long long : LM_CAT3_(prefix, u, __LONG_LONG_WIDTH__) , \ + signed char : LM_CAT3_(prefix, s, __SCHAR_WIDTH__) , \ + signed short : LM_CAT3_(prefix, s, __SHRT_WIDTH__) , \ + signed int : LM_CAT3_(prefix, s, __INT_WIDTH__) , \ + signed long : LM_CAT3_(prefix, s, __LONG_WIDTH__) , \ + signed long long : LM_CAT3_(prefix, s, __LONG_LONG_WIDTH__) ) +#define fmt_print_base2(w, val) (_fmt_intswitch((val), _fmt_print_base2_)((w), (val))) +#define fmt_print_base8(w, val) (_fmt_intswitch((val), _fmt_print_base8_)((w), (val))) +#define fmt_print_base10(w, val) (_fmt_intswitch((val), _fmt_print_base10_)((w), (val))) +#define fmt_print_base16(w, val) (_fmt_intswitch((val), _fmt_print_base16_)((w), (val))) + +/* Booleans. */ +void fmt_print_bool(lo_interface fmt_dest w, bool b); + +/* The high-level fmt_print() interface ***************************************/ + +#define fmt_print(w, ...) do { LM_FOREACH_PARAM_(_fmt_param, (w), __VA_ARGS__) } while (0) +#define _fmt_param(w, arg) \ + LM_IF(LM_IS_TUPLE(arg))( \ + _fmt_param_tuple LM_EAT() (w, LM_EXPAND arg) \ + )( \ + _fmt_param_nontuple(w, arg) \ + ); +#define _fmt_param_tuple(w, fn, ...) fmt_print_##fn(w __VA_OPT__(,) __VA_ARGS__) +#define _fmt_param_nontuple(w, val) _Generic((val), \ + unsigned char : LM_CAT2_(_fmt_print_base10_u, __SCHAR_WIDTH__) , \ + unsigned short : LM_CAT2_(_fmt_print_base10_u, __SHRT_WIDTH__) , \ + unsigned int : LM_CAT2_(_fmt_print_base10_u, __INT_WIDTH__) , \ + unsigned long : LM_CAT2_(_fmt_print_base10_u, __LONG_WIDTH__) , \ + unsigned long long : LM_CAT2_(_fmt_print_base10_u, __LONG_LONG_WIDTH__) , \ + signed char : LM_CAT2_(_fmt_print_base10_s, __SCHAR_WIDTH__) , \ + signed short : LM_CAT2_(_fmt_print_base10_s, __SHRT_WIDTH__) , \ + signed int : LM_CAT2_(_fmt_print_base10_s, __INT_WIDTH__) , \ + signed long : LM_CAT2_(_fmt_print_base10_s, __LONG_WIDTH__) , \ + signed long long : LM_CAT2_(_fmt_print_base10_s, __LONG_LONG_WIDTH__) , \ + char * : fmt_print_str , \ + const char * : fmt_print_str , \ + bool : fmt_print_bool )(w, val) + +/** Same as fmt_print(), but usable from inside of fmt_print(). */ +#define fmt_print2(w, ...) do { LM_FOREACH_PARAM2_(_fmt_param2, (w), __VA_ARGS__) } while (0) +#define _fmt_param2(...) _LM_DEFER2(_fmt_param_indirect)()(__VA_ARGS__) +#define _fmt_param_indirect() _fmt_param + +/* print-to-memory ************************************************************/ + +struct fmt_buf { + void *dat; + size_t len, cap; +}; +LO_IMPLEMENTATION_H(fmt_dest, struct fmt_buf, fmt_buf); + +#define fmt_snprint(buf, n, ...) ({ \ + struct fmt_buf _w = { .dat = buf, .cap = n }; \ + lo_interface fmt_dest w = LO_BOX(fmt_dest, &_w); \ + fmt_print(w, __VA_ARGS__); \ + if (_w.len < _w.cap) \ + ((char *)_w.dat)[_w.len] = '\0'; \ + _w.len; \ +}) + +#define fmt_asprint(...) ({ \ + struct fmt_buf _w = {}; \ + lo_interface fmt_dest w = LO_BOX(fmt_dest, &_w); \ + fmt_print(w, __VA_ARGS__); \ + while (_w.cap <= _w.len) { \ + _w.cap = _w.len + 1; \ + _w.len = 0; \ + _w.dat = realloc(_w.dat, _w.cap); \ + fmt_print(w, __VA_ARGS__); \ + } \ + ((char *)_w.dat)[_w.len] = '\0'; \ + _w.dat; \ +}) + +/* justify ********************************************************************/ + +#define fmt_print_ljust(w, width, fillchar, ...) do { \ + size_t beg = LO_CALL(w, tell); \ + fmt_print2(w, __VA_ARGS__); \ + while ((LO_CALL(w, tell) - beg) < width) \ + fmt_print_byte(w, fillchar); \ +} while (0) + +#define fmt_print_rjust(w, width, fillchar, ...) do { \ + struct fmt_buf _discard = {}; \ + lo_interface fmt_dest discard = LO_BOX2(fmt_dest, &_discard); \ + fmt_print2(discard, __VA_ARGS__); \ + while (_discard.len++ < width) \ + fmt_print_byte(w, fillchar); \ + fmt_print2(w, __VA_ARGS__); \ +} while (0) + +void fmt_print_base16_u8_(lo_interface fmt_dest w, uint8_t x); +void fmt_print_base16_u16_(lo_interface fmt_dest w, uint16_t x); +void fmt_print_base16_u32_(lo_interface fmt_dest w, uint32_t x); +void fmt_print_base16_u64_(lo_interface fmt_dest w, uint64_t x); +void fmt_print_ptr(lo_interface fmt_dest w, void *ptr); + +#endif /* _LIBMISC_FMT_H_ */ diff --git a/libmisc/include/libmisc/hash.h b/libmisc/include/libmisc/hash.h index 91e6b10..029bd3b 100644 --- a/libmisc/include/libmisc/hash.h +++ b/libmisc/include/libmisc/hash.h @@ -1,31 +1,21 @@ /* libmisc/hash.h - General-purpose hash 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 */ #ifndef _LIBMISC_HASH_H_ #define _LIBMISC_HASH_H_ -#include <stdint.h> /* for uint{n}_t */ #include <stddef.h> /* for size_t */ +#include <stdint.h> /* for uint{n}_t */ -/* djb2 hash */ -typedef uint32_t hash_t; -static inline void hash_init(hash_t *hash) { - *hash = 5381; -} -static inline void hash_write(hash_t *hash, void *dat, size_t len) { - for (size_t i = 0; i < len; i++) - *hash = (*hash * 33) + (hash_t)(((unsigned char *)dat)[i]); -} +/* base */ +typedef uint32_t hash_t; /* size subject to change */ +void hash_init(hash_t *hash); +void hash_write(hash_t *hash, void *dat, size_t len); /* utilities */ -static inline hash_t hash(void *dat, size_t len) { - hash_t h; - hash_init(&h); - hash_write(&h, dat, len); - return h; -} +hash_t hash(void *dat, size_t len); #endif /* _LIBMISC_HASH_H_ */ diff --git a/libmisc/include/libmisc/linkedlist.h b/libmisc/include/libmisc/linkedlist.h new file mode 100644 index 0000000..b6ff688 --- /dev/null +++ b/libmisc/include/libmisc/linkedlist.h @@ -0,0 +1,108 @@ +/* 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_ + +/* low-level (intrusive) singly linked list ***********************************/ + +struct _slist_node { + struct _slist_node *rear; +}; + +struct _slist_root { + struct _slist_node *front, *rear; +}; + +void _slist_push_to_rear(struct _slist_root *root, struct _slist_node *node); +void _slist_pop_from_front(struct _slist_root *root); + +/* low-level (intrusive) doubly linked list ***********************************/ + +struct _dlist_node { + struct _dlist_node *front, *rear; +}; + +struct _dlist_root { + struct _dlist_node *front, *rear; +}; + +void _dlist_push_to_rear(struct _dlist_root *root, struct _dlist_node *node); +void _dlist_remove(struct _dlist_root *root, struct _dlist_node *node); +void _dlist_pop_from_front(struct _dlist_root *root); + +/* singly linked list (non-intrusive) *****************************************/ + +#define SLIST_DECLARE(NAME) \ + struct NAME##_node; \ + struct NAME { \ + struct NAME##_node *front, *rear; \ + struct NAME *_slist_root_typ[0]; \ + } + +#define SLIST_DECLARE_NODE(NAME, VAL_T) \ + struct NAME##_node { \ + struct NAME##_node *rear; \ + VAL_T val; \ + } + +#define slist_push_to_rear(ROOT, NODE) { \ + /* These temporary variables are to get the compiler to check \ + * the types. */ \ + typeof(*(ROOT)->_slist_root_typ[0]) *_rootp = ROOT; \ + typeof(*_rootp->front) *_nodep = NODE; \ + _slist_push_to_rear((struct _slist_root *)_rootp, \ + (struct _slist_node *)_nodep); \ +} while (0) + +#define slist_pop_from_front(ROOT) { \ + /* This temporary variables are to get the compiler to check \ + * the type. */ \ + typeof(*(ROOT)->_slist_root_typ[0]) *_rootp = ROOT; \ + _slist_pop_from_front((struct _slist_root *)_rootp); \ +} while (0) + +/* doubly linked list (non-intrusive) *****************************************/ + +#define DLIST_DECLARE(NAME) \ + struct NAME##_node; \ + struct NAME { \ + struct NAME##_node *front, *rear; \ + struct NAME *_dlist_root_typ[0]; \ + } + +#define DLIST_DECLARE_NODE(NAME, VAL_T) \ + struct NAME##_node { \ + struct NAME##_node *front, *rear; \ + VAL_T val; \ + } + +#define dlist_push_to_rear(ROOT, NODE) { \ + /* These temporary variables are to get the compiler to check \ + * the types. */ \ + typeof(*(ROOT)->_dlist_root_typ[0]) *_rootp = ROOT; \ + typeof(*_rootp->front) *_nodep = NODE; \ + _dlist_push_to_rear((struct _dlist_root *)_rootp, \ + (struct _dlist_node *)_nodep); \ +} while (0) + +#define dlist_remove(ROOT, NODE) { \ + /* These temporary variables are to get the compiler to check \ + * the types. */ \ + typeof(*(ROOT)->_dlist_root_typ[0]) *_rootp = ROOT; \ + typeof(*_rootp->front) *_nodep = NODE; \ + _dlist_remove((struct _dlist_root *)_rootp, \ + (struct _dlist_node *)_nodep); \ +} while (0) + +#define dlist_pop_from_front(ROOT) { \ + /* This temporary variables are to get the compiler to check \ + * the type. */ \ + typeof(*(ROOT)->_dlist_root_typ[0]) *_rootp = ROOT; \ + _dlist_pop_from_front((struct _dlist_root *)_rootp); \ +} while (0) + +#endif /* _LIBMISC_LINKEDLIST_H_ */ diff --git a/libmisc/include/libmisc/log.h b/libmisc/include/libmisc/log.h index 37da20b..c40b642 100644 --- a/libmisc/include/libmisc/log.h +++ b/libmisc/include/libmisc/log.h @@ -1,31 +1,37 @@ /* libmisc/log.h - stdio logging * - * 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_LOG_H_ #define _LIBMISC_LOG_H_ -#ifndef LOG_NAME - #error "each compilation unit that includes <libmisc/log.h> must define LOG_NAME" -#endif +#include <stdint.h> /* for uint8_t */ + +#include <libmisc/_intercept.h> +#include <libmisc/fmt.h> +#include <libmisc/macro.h> #ifdef NDEBUG #define _LOG_NDEBUG 1 #else #define _LOG_NDEBUG 0 #endif -#define __LOG_STR(x) #x -#define _LOG_STR(x) __LOG_STR(x) -#define __LOG_CAT3(a, b, c) a ## b ## c -#define _LOG_CAT3(a, b, c) __LOG_CAT3(a, b, c) -[[format(printf, 1, 2)]] int _log_printf(const char *format, ...); +const char *const_byte_str(uint8_t b); + +extern lo_interface fmt_dest _log_dest; -#define errorf(fmt, ...) do { _log_printf("error: " _LOG_STR(LOG_NAME) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) -#define infof(fmt, ...) do { _log_printf("info : " _LOG_STR(LOG_NAME) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) -#define debugf(fmt, ...) do { if (_LOG_CAT3(CONFIG_, LOG_NAME, _DEBUG) && !_LOG_NDEBUG) \ - _log_printf("debug: " _LOG_STR(LOG_NAME) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) +#define log_n_errorln(nam, ...) fmt_print(_log_dest, "error: " LM_STR_(nam) ": ", __VA_ARGS__, "\n") +#define log_n_infoln(nam, ...) fmt_print(_log_dest, "info : " LM_STR_(nam) ": ", __VA_ARGS__, "\n") +#define log_n_debugln(nam, ...) do { if (LM_CAT3_(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \ + fmt_print(_log_dest, "debug: " LM_STR_(nam) ": ", __VA_ARGS__, "\n"); } while (0) #endif /* _LIBMISC_LOG_H_ */ + +#if defined(LOG_NAME) && !defined(log_errorln) +#define log_errorln(...) log_n_errorln(LOG_NAME, __VA_ARGS__) +#define log_infoln(...) log_n_infoln(LOG_NAME, __VA_ARGS__) +#define log_debugln(...) log_n_debugln(LOG_NAME, __VA_ARGS__) +#endif diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h new file mode 100644 index 0000000..48f52e5 --- /dev/null +++ b/libmisc/include/libmisc/macro.h @@ -0,0 +1,187 @@ +/* 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_ + +#include <libmisc/assert.h> + +/* C: syntax ******************************************************************/ + +#define LM_FORCE_SEMICOLON static_assert(1, "force semicolon") + +#define LM_PARTIAL_SWITCH(VAL) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wswitch-enum\"") \ + switch (VAL) \ + _Pragma("GCC diagnostic pop") \ + +/* C: 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]] + +/* C: types *******************************************************************/ + +/* If it's a pointer instead of an array, then typeof(&ptr[0]) == typeof(ptr) */ +#define _LM_IS_ARRAY(ary) (!__builtin_types_compatible_p(typeof(&(ary)[0]), typeof(ary))) +#define LM_ARRAY_LEN(ary) ( (sizeof(ary)/sizeof((ary)[0])) + static_assert_as_expr(_LM_IS_ARRAY(ary)) ) + +#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; \ +}) + +/* C: 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) */ + +/* CPP: strings ***************************************************************/ + +#define LM_STR(x) #x +#define LM_STR_(x) LM_STR(x) + +/* CPP: 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) + +/* CPP: macro arguments *******************************************************/ + +#define LM_FIRST(a, ...) a +#define LM_SECOND(a, b, ...) b + +#define LM_FIRST_(...) LM_FIRST(__VA_ARGS__) +#define LM_SECOND_(...) LM_SECOND(__VA_ARGS__) + +#define LM_EAT(...) +#define LM_EXPAND(...) __VA_ARGS__ + +/* CPP: 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 + +/* CPP: tuples ****************************************************************/ + +#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x) +#define _LM_IS_TUPLE(...) LM_SENTINEL() +/* For LM_IS_EMPTY_TUPLE: + * + * Given + * + * #define HELPER(...) B, __VA_OPT__(C,) D + * + * then evaluating the sequence of tokens `HELPER x , A` will give us a + * new sequence of tokens according to the following table: + * + * not a tuple : HELPER x , A + * tuple, nonempty: B , C , D , A + * tuple, empty : B , D , A + * + * Looking at this table, it is clear that we must look at the 2nd + * resulting comma-separated-value (argument), and set A=false, + * C=false, D=true (and B doesn't matter). + */ +#define LM_IS_EMPTY_TUPLE(x) LM_SECOND_(_LM_IS_EMPTY_TUPLE x, LM_F) +#define _LM_IS_EMPTY_TUPLE(...) bogus, __VA_OPT__(LM_F,) LM_T + +/* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */ +#define _LM_TUPLES_COMMA(...) (__VA_ARGS__), +#define LM_TUPLES_IS_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 + +/* CPP: iteration *************************************************************/ + +/* The desire to support a high number of iterations is in competition + * with the desire for short compile times. 16 is the lowest + * power-of-2 for which the current code compiles. */ +#define _LM_EVAL _LM_EVAL__16 +#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__ + +#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()() + +/** + * LM_FOREACH_PARAM(func, (fixedparams), params...) calls + * func(fixedparams..., param) for each param. + * + * BUG: LM_FOREACH_PARAM is limited to (16*2)-1=31 params. + */ +#define LM_FOREACH_PARAM(func, fixedparams, ...) _LM_EVAL(_LM_FOREACH_PARAM(func, fixedparams, __VA_ARGS__)) +#define _LM_FOREACH_PARAM(func, fixedparams, ...) _LM_FOREACH_PARAM_ITEM(func, fixedparams, __VA_ARGS__, ()) +#define _LM_FOREACH_PARAM_FIXEDPARAMS(fixedparams) _LM_FOREACH_PARAM_FIXEDPARAMS_inner fixedparams +#define _LM_FOREACH_PARAM_FIXEDPARAMS_inner(...) __VA_ARGS__ __VA_OPT__(,) +#define _LM_FOREACH_PARAM_ITEM(func, fixedparams, param, ...) \ + LM_IF(LM_IS_EMPTY_TUPLE(param))()( \ + _LM_DEFER2(func)(_LM_FOREACH_PARAM_FIXEDPARAMS(fixedparams) param) \ + _LM_DEFER2(_LM_FOREACH_PARAM_ITEM_indirect)()(func, fixedparams, __VA_ARGS__) \ + ) +#define _LM_FOREACH_PARAM_ITEM_indirect() _LM_FOREACH_PARAM_ITEM + +/** The same as LM_FOREACH_PARAM(), but callable from inside of LM_FOREACH_PARAM(). */ +#define LM_FOREACH_PARAM2(...) _LM_DEFER2(_LM_FOREACH_PARAM_ITEM_indirect)()(__VA_ARGS__, ()) + +/** The same as above, but evaluates the arguments first. */ +#define LM_FOREACH_PARAM_(...) LM_FOREACH_PARAM(__VA_ARGS__) +#define LM_FOREACH_PARAM2_(...) LM_FOREACH_PARAM2(__VA_ARGS__) + +/** + * LM_FOREACH_TUPLE( (tup1) (tup2) (tup3), func, args...) calls + * func(args..., tup...) for each tuple. + * + * BUG: LM_FOREACH_TUPLE is limited to (16*2)-1=31 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_IS_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 + +/** The same as LM_FOREACH_TUPLE(), but callable from inside of LM_FOREACH_TUPLE(). */ +#define LM_FOREACH_TUPLE2(...) _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(__VA_ARGS__) + +/* CPP: wrap-cc extensions ****************************************************/ + +#ifdef __LIBMISC_ENHANCED_CPP__ +/** + * `LM_DEFAPPEND(macro, val)` is like `#define macro val`, but can (1) + * be used from inside of a macro, and (2) appends to a value if it is + * already defined with LM_DEFAPPEND. There are lots of edge-cases, + * don't get cute. + */ +#define LM_DEFAPPEND(macro, ...) __xx__LM_DEFAPPEND__xx__(#macro, #__VA_ARGS__) LM_FORCE_SEMICOLON +#define LM_DEFAPPEND_(macro, ...) _LM_DEFAPPEND_(#macro, __VA_ARGS__) +#define _LM_DEFAPPEND_(macrostr, ...) __xx__LM_DEFAPPEND__xx__(macrostr, #__VA_ARGS__) LM_FORCE_SEMICOLON +#endif + +#endif /* _LIBMISC_MACRO_H_ */ diff --git a/libmisc/include/libmisc/map.h b/libmisc/include/libmisc/map.h new file mode 100644 index 0000000..113bc0e --- /dev/null +++ b/libmisc/include/libmisc/map.h @@ -0,0 +1,145 @@ +/* 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 <stddef.h> /* for size_t */ +#include <stdint.h> /* for uint8_t */ + +#include <libmisc/linkedlist.h> + +/* Type ***********************************************************************/ + +DLIST_DECLARE(_map_kv_list); + +struct _map { + size_t len; + size_t nbuckets; + struct _map_kv_list *buckets; + + unsigned int 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##_kv { \ + uint8_t flags; \ + KEY_T key; \ + VAL_T val; \ + }; \ + DLIST_DECLARE_NODE(_##MAPNAME##_kv_list, struct _##MAPNAME##_kv); \ + struct MAPNAME { \ + struct _map core; \ + struct _##MAPNAME##_kv_list_node 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].val.key); \ + (M)->core.sizeof_v = sizeof((M)->kv_typ[0].val.val); \ + (M)->core.offsetof_k = offsetof(typeof((M)->kv_typ[0]), val.key); \ + (M)->core.offsetof_v = offsetof(typeof((M)->kv_typ[0]), val.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].val.key) _k = K; \ + (typeof((M)->kv_typ[0].val.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].val.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].val.key) _k = K; \ + typeof((M)->kv_typ[0].val.val) _v = __VA_ARGS__; \ + (typeof((M)->kv_typ[0].val.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_iter { + struct _map *m; + void *keyp; + void **valpp; + + size_t i; + struct _map_kv_list_node *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].val.key) KNAME; _once_##CNT;) \ + for (typeof((M)->kv_typ[0].val.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/obj.h b/libmisc/include/libmisc/obj.h new file mode 100644 index 0000000..6afa391 --- /dev/null +++ b/libmisc/include/libmisc/obj.h @@ -0,0 +1,164 @@ +/* libmisc/obj.h - A simple Go-ish object system + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_OBJ_H_ +#define _LIBMISC_OBJ_H_ + +#include <libmisc/macro.h> + +/** + * Use `lo_interface` similarly to how you would use + * `struct`/`enum`/`union` when writing the type of an interface + * value. + */ +#define lo_interface struct + +/** + * Use `LO_INTERFACE` in a .h file to define an interface. + * + * First define a macro named `{iface_name}_LO_IFACE` consisting of a + * series of calls to LO_NEST and/or LO_FUNC, then call + * `LO_INTERFACE({iface_name})`: + * + * #define myiface_LO_IFACE \ + * LO_NEST(wrapped_iface_name) \ + * LO_FUNC(ret_type, func_name, args...) + * LO_INTERFACE(myiface) + * + * Use `lo_interface {iface_name}` as the type of this interface; it + * should not be a pointer type. + */ +#define LO_NEST(_ARG_child_iface_name) \ + (lo_nest, _ARG_child_iface_name) +#define LO_FUNC(_ARG_ret_type, _ARG_func_name, ...) \ + (lo_func, _ARG_ret_type, _ARG_func_name __VA_OPT__(,) __VA_ARGS__) +#define LO_INTERFACE(_ARG_iface_name) \ + struct _lo_##_ARG_iface_name##_vtable { \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IFACE_VTABLE) \ + }; \ + struct _ARG_iface_name { \ + void *self; \ + const struct _lo_##_ARG_iface_name##_vtable *vtable; \ + }; \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IFACE_PROTO, _ARG_iface_name) \ + LM_FORCE_SEMICOLON + +#define _LO_IFACE_VTABLE(_tuple_typ, ...) _LO_IFACE_VTABLE_##_tuple_typ(__VA_ARGS__) +#define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) \ + const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; \ + LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE2) +#define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) \ + _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); + +#define _LO_IFACE_VTABLE_indirect() _LO_IFACE_VTABLE +#define _LO_IFACE_VTABLE2(...) _LM_DEFER2(_LO_IFACE_VTABLE_indirect)()(__VA_ARGS__) + +#define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__) +#define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \ + LM_DEFAPPEND(_LO_REGISTRY_##_ARG_child_iface_name, \ + (lo_interface _ARG_iface_name, _LO_BOX_##_ARG_iface_name##_as_##_ARG_child_iface_name)); \ + LM_DEFAPPEND(_LO_BOX_##_ARG_iface_name##_as_##_ARG_child_iface_name(obj), \ + { .self = obj.self, .vtable = obj.vtable->_lo_##_ARG_child_iface_name##_vtable }); +#define _LO_IFACE_PROTO_lo_func(_ARG_iface_name, _ARG_ret_type, _ARG_func_name, ...) \ + /* empty */ + +/** + * `LO_BOX(iface_name, obj)` boxes `obj` as a `lo_interface + * iface_name`. `obj` must be one of: + * + * - A pointer to a value that implements `lo_interface iface_name` + * - An already-boxed instance of `lo_interface iface_name` + * - An already-boxed instance of another interface that + * `iface_name` inherits from. + */ +#define LO_BOX(_ARG_iface_name, obj) _Generic((obj), \ + lo_interface _ARG_iface_name: obj \ + LM_FOREACH_TUPLE(_LO_REGISTRY_##_ARG_iface_name, \ + _LO_BOX, _ARG_iface_name, obj)) +#define LO_BOX2(_ARG_iface_name, obj) _Generic((obj), \ + lo_interface _ARG_iface_name: obj \ + LM_FOREACH_TUPLE2(_LO_REGISTRY_##_ARG_iface_name, \ + _LO_BOX, _ARG_iface_name, obj)) +#define _LO_BOX(_ARG_iface_name, obj, typ, boxfn) \ + , typ: (lo_interface _ARG_iface_name)boxfn(obj) + +/** + * `LO_NULL(iface_name)` is the null/nil/zero value for `lo_interface {iface_name}`. + */ +#define LO_NULL(_ARG_iface_name) ((lo_interface _ARG_iface_name){}) + +/** + * `LO_IS_NULL(iface_val)` returns whether `iface_val` is LO_NULL. + */ +#define LO_IS_NULL(_ARG_iface_val) ((_ARG_iface_val).vtable == NULL) + +/** + * `LO_IFACE_EQ(a, b)` returns whether the interface values `a` and + * `b` are the same object. + */ +#define LO_EQ(_ARG_iface_val_a, _ARG_iface_val_b) \ + ((_ARG_iface_val_a).self == (_ARG_iface_val_b).self) + +/** + * Use LO_CALL(obj, method_name, args...) to call a method on an `lo_interface`. + */ +#define LO_CALL(_ARG_obj, _ARG_meth, ...) \ + (_ARG_obj).vtable->_ARG_meth((_ARG_obj).self __VA_OPT__(,) __VA_ARGS__) + +/** + * Use `LO_IMPLEMENTATION_H(iface_name, impl_type, impl_name)` in a .h + * file to declare that `{impl_type}` implements the `{iface_name}` + * interface with functions named `{impl_name}_{method_name}`. + * + * You must also call the LO_IMPLEMENTATION_C in a single .c file. + */ +#define LO_IMPLEMENTATION_H(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \ + /* Vtable. */ \ + extern const struct _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \ + /* Boxing. */ \ + LM_DEFAPPEND(_LO_REGISTRY_##_ARG_iface_name, \ + (_ARG_impl_type *, _LO_BOX_##_ARG_impl_name##_as_##_ARG_iface_name)); \ + LM_DEFAPPEND(_LO_BOX_##_ARG_impl_name##_as_##_ARG_iface_name(obj), \ + { .self = obj, .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable }); \ + LM_FORCE_SEMICOLON + +/** + * Use `LO_IMPLEMENTATION_C(iface_name, impl_type, impl_name[, static])` in a .c + * file to declare that `{impl_type}` implements the `{iface_name}` interface + * with functions named `{impl_name}_{method_name}`. + * + * You must also call the LO_IMPLEMENTATION_H in the corresponding .h file. + * + * If `iface_name` contains a nested interface, then the + * implementation of the nested interfaces must be declared with + * `LO_IMPLEMENTATION_C` first. + */ +#define LO_IMPLEMENTATION_C(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name, ...) \ + /* Method prototypes. */ \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IMPL_PROTO, _ARG_impl_type, _ARG_impl_name, __VA_ARGS__) \ + /* Vtable. */ \ + const struct _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable = { \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IMPL_VTABLE, _ARG_impl_name) \ + } + +#define _LO_IMPL_PROTO( _ARG_impl_type, _ARG_impl_name, _ARG_quals, _tuple_typ, ...) _LO_IMPL_PROTO_##_tuple_typ(_ARG_impl_type, _ARG_impl_name, _ARG_quals, __VA_ARGS__) +#define _LO_IMPL_PROTO_lo_nest(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_child_iface_name) /* empty */ +#define _LO_IMPL_PROTO_lo_func(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_ret_type, _ARG_func_name, ...) _ARG_quals _ARG_ret_type _ARG_impl_name##_##_ARG_func_name(_ARG_impl_type * __VA_OPT__(,) __VA_ARGS__); + +#define _LO_IMPL_VTABLE(_ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE_##_tuple_typ(_ARG_impl_name, __VA_ARGS__) +#define _LO_IMPL_VTABLE_lo_nest(_ARG_impl_name, _ARG_child_iface_name) ._lo_##_ARG_child_iface_name##_vtable = &_lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IMPL_VTABLE2, _ARG_impl_name) +#define _LO_IMPL_VTABLE_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name, + +#define _LO_IMPL_VTABLE_indirect() _LO_IMPL_VTABLE +#define _LO_IMPL_VTABLE2(...) _LM_DEFER2(_LO_IMPL_VTABLE_indirect)()(__VA_ARGS__) + +#endif /* _LIBMISC_OBJ_H_ */ diff --git a/libmisc/include/libmisc/private.h b/libmisc/include/libmisc/private.h index bc5e7ad..5a8777c 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_PRIVATE_H_ #define _LIBMISC_PRIVATE_H_ -/* primitive utilities */ +#include <libmisc/macro.h> -#define _SECOND(a, b, ...) b - -#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__);) +#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 {) LM_FORCE_SEMICOLON +#define END_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))(LM_FORCE_SEMICOLON)(} LM_CAT2_(_PRIVATE_, __COUNTER__)) #endif /* _LIBMISC_PRIVATE_H_ */ diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h index 8072841..ca16f42 100644 --- a/libmisc/include/libmisc/rand.h +++ b/libmisc/include/libmisc/rand.h @@ -1,46 +1,18 @@ /* 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 */ #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> +#include <stdint.h> /* for uint{n}_t */ /** * 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 = (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 = (random() << 62) | (random() << 31) | random(); - } while (rnd >= fair_cnt); - return rnd % cnt; - } - assert_notreached("cnt is out of bounds"); -} +uint64_t rand_uint63n(uint64_t cnt); #endif /* _LIBMISC_RAND_H_ */ diff --git a/libmisc/include/libmisc/utf8.h b/libmisc/include/libmisc/utf8.h new file mode 100644 index 0000000..54fcc92 --- /dev/null +++ b/libmisc/include/libmisc/utf8.h @@ -0,0 +1,25 @@ +/* libmisc/utf8.h - UTF-8 routines + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_UTF8_H_ +#define _LIBMISC_UTF8_H_ + +#include <stddef.h> /* for size_t */ +#include <stdint.h> /* for uint{n}_t */ + +/** + * Decode the codepoint starting at `str` and consuming at most `len` + * bytes. Invalid UTF-8 is indicated with chlen=0. For valid UTF-8, + * chlen is always in the range [1, 4]. + */ +void utf8_decode_codepoint(const uint8_t *str, size_t len, uint32_t *ret_ch, uint8_t *ret_chlen); + +bool _utf8_is_valid(const uint8_t *str, size_t len, bool forbid_nul); + +#define utf8_is_valid(str, len) _utf8_is_valid(str, len, false) +#define utf8_is_valid_without_nul(str, len) _utf8_is_valid(str, len, true) + +#endif /* _LIBMISC_UTF8_H_ */ diff --git a/libmisc/include/libmisc/vcall.h b/libmisc/include/libmisc/vcall.h deleted file mode 100644 index 9b54c06..0000000 --- a/libmisc/include/libmisc/vcall.h +++ /dev/null @@ -1,28 +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 <stddef.h> /* for offsetof() */ - -#include <libmisc/assert.h> - -#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..d0e3602 --- /dev/null +++ b/libmisc/intercept.c @@ -0,0 +1,20 @@ +/* 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 <stdio.h> /* for putchar() */ +#include <stdlib.h> /* for abort() */ + +#include <libmisc/_intercept.h> + +[[gnu::weak]] +void __lm_putchar(unsigned char c) { + (void)putchar(c); +} + +[[gnu::weak]] +void __lm_abort(void) { + abort(); +} diff --git a/libmisc/linkedlist.c b/libmisc/linkedlist.c new file mode 100644 index 0000000..71a0aa9 --- /dev/null +++ b/libmisc/linkedlist.c @@ -0,0 +1,64 @@ +/* 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/assert.h> + +#include <libmisc/linkedlist.h> + +/* singly linked list *********************************************************/ + +void _slist_push_to_rear(struct _slist_root *root, struct _slist_node *node) { + assert(root); + node->rear = NULL; + if (root->rear) + root->rear->rear = node; + else + root->front = node; + root->rear = node; +} + +void _slist_pop_from_front(struct _slist_root *root) { + assert(root); + assert(root->front); + root->front = root->front->rear; + if (!root->front) + root->rear = NULL; +} + +/* doubly linked list *********************************************************/ + +void _dlist_push_to_rear(struct _dlist_root *root, struct _dlist_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 _dlist_remove(struct _dlist_root *root, struct _dlist_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 _dlist_pop_from_front(struct _dlist_root *root) { + assert(root); + assert(root->front); + _dlist_remove(root, root->front); +} diff --git a/libmisc/log.c b/libmisc/log.c index a1ec10f..7e917c6 100644 --- a/libmisc/log.c +++ b/libmisc/log.c @@ -1,19 +1,293 @@ /* libmisc/log.c - stdio logging * - * 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 */ -#include <stdio.h> /* for vprintf() */ -#include <stdarg.h> /* for va_list, va_start(), va_end() */ +#include <stdint.h> /* for uint{n}_t */ -#define LOG_NAME +#include <libmisc/assert.h> /* for static_assert() */ + +#include <libmisc/_intercept.h> #include <libmisc/log.h> -int _log_printf(const char *format, ...) { - va_list va; - va_start(va, format); - int ret = vprintf(format, va); - va_end(va); - return ret; +struct log_stdout {}; +LO_IMPLEMENTATION_H(fmt_dest, struct log_stdout, log_stdout); +LO_IMPLEMENTATION_C(fmt_dest, struct log_stdout, log_stdout, static); + +static size_t log_bytes = 0; + +static void log_stdout_putb(struct log_stdout *, uint8_t b) { + __lm_putchar(b); + log_bytes++; +} + +static size_t log_stdout_tell(struct log_stdout *) { + return log_bytes; +} + +lo_interface fmt_dest _log_dest = { .vtable = &_lo_log_stdout_fmt_dest_vtable }; + +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/map.c b/libmisc/map.c new file mode 100644 index 0000000..d1b2a57 --- /dev/null +++ b/libmisc/map.c @@ -0,0 +1,230 @@ +/* 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/alloc.h> +#include <libmisc/assert.h> +#include <libmisc/hash.h> +#include <libmisc/map.h> + +#define FLAG_ITER (UINT8_C(1)<<0) +#define FLAG_DEL (UINT8_C(1)<<1) + +/* Internal utilities *********************************************************/ + +struct _map_kv { + uint8_t flags; + /* opaque key; */ + /* opaque val; */ +}; +DLIST_DECLARE_NODE(_map_kv_list, struct _map_kv); + +static inline void *_map_kv_keyp(struct _map *m, struct _map_kv_list_node *kv) { + assert(m); + assert(kv); + return ((void*)kv)+m->offsetof_k; +} +static inline void *_map_kv_valp(struct _map *m, struct _map_kv_list_node *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, + struct _map_kv_list **ret_bucket, + struct _map_kv_list_node **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_list_node *kv = (*ret_bucket)->front; kv; kv = kv->rear) { + if (!(kv->val.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); + struct _map_kv_list *new_buckets = heap_alloc(new_nbuckets, struct _map_kv_list); + for (size_t i = 0; i < m->nbuckets; i++) { + while (m->buckets[i].front) { + struct _map_kv_list_node *kv = m->buckets[i].front; + dlist_pop_from_front(&m->buckets[i]); + hash_t h = hash(_map_kv_keyp(m, kv), m->sizeof_k); + dlist_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; + struct _map_kv_list *bucket; + struct _map_kv_list_node *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; + struct _map_kv_list *bucket; + struct _map_kv_list_node *kv; + _map_lookup(m, keyp, &h, &bucket, &kv); + + if (!kv) + return false; + if (kv->val.flags & FLAG_ITER) { + kv->val.flags |= FLAG_DEL; + } else { + dlist_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; + struct _map_kv_list *bucket; + struct _map_kv_list_node *old; + _map_lookup(m, keyp, &h, &bucket, &old); + + if (old) { + dlist_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_list_node *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); + dlist_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_list_node *kv = m->buckets[i].front; + dlist_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 = state->m->buckets[state->i].front; + } else { + struct _map_kv_list_node *old_kv = state->kv; + state->kv = old_kv->rear; + + old_kv->val.flags &= ~FLAG_ITER; + if (old_kv->val.flags & FLAG_DEL) { + dlist_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 = state->m->buckets[state->i].front; + } + } + state->kv->val.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/rand.c b/libmisc/rand.c new file mode 100644 index 0000000..d1643ee --- /dev/null +++ b/libmisc/rand.c @@ -0,0 +1,38 @@ +/* libmisc/rand.c - Non-crytpographic random-number utilities + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdlib.h> /* for random() */ + +#include <libmisc/assert.h> + +#include <libmisc/rand.h> + +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"); +} diff --git a/libmisc/tests/test_assert.c b/libmisc/tests/test_assert.c index 5b28561..290b073 100644 --- a/libmisc/tests/test_assert.c +++ b/libmisc/tests/test_assert.c @@ -1,45 +1,54 @@ /* libmisc/tests/test_assert.c - Tests for <libmisc/assert.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 */ #include <setjmp.h> -#include <stdbool.h> -#include <string.h> #include <stdlib.h> +#include <string.h> +#include <libmisc/_intercept.h> #include <libmisc/assert.h> +#include <libmisc/fmt.h> +#include <libmisc/macro.h> #include "test.h" -#define UNUSED(name) - /* Intercept failures and logging *********************************************/ -bool global_failed; -char *global_log; -jmp_buf global_env; +static bool global_failed; +static struct fmt_buf global_log; +static jmp_buf global_env; #define with_intercept() ({ \ global_failed = false; \ - if (global_log) \ - free(global_log); \ - global_log = NULL; \ + global_log_clear(); \ setjmp(global_env) == 0; \ }) -[[noreturn]] void abort(void) { +void __lm_abort(void) { global_failed = true; longjmp(global_env, 1); } -#define __builtin_unreachable() test_assert(0) +void __lm_putchar(unsigned char c) { + if (global_log.len+1 >= global_log.cap) { + global_log.cap += 16; + global_log.dat = realloc(global_log.dat, global_log.cap); + memset(global_log.dat + global_log.len, 0, global_log.cap - global_log.len); + } + ((uint8_t *)global_log.dat)[global_log.len++] = (uint8_t)c; +} -int vprintf(const char *format, va_list ap) { - return vasprintf(&global_log, format, ap); +static void global_log_clear(void) { + if (global_log.dat) + memset(global_log.dat, 0, global_log.cap); + global_log.len = 0; } +#define __builtin_unreachable() test_assert(0) + /* Utilities ******************************************************************/ #define test_should_succeed(test) do { \ @@ -47,44 +56,45 @@ int vprintf(const char *format, va_list ap) { test; \ } \ test_assert(global_failed == false); \ - test_assert(global_log == NULL); \ + test_assert(global_log.len == 0); \ } 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); \ - } \ +#define test_should_fail(test, exp_log) do { \ + if (with_intercept()) { \ + test; \ + } \ + test_assert(global_failed == true); \ + if (!(global_log.len != 0 && \ + strcmp(global_log.dat, exp_log) == 0)) { \ + printf("exp = \"%s\"\n" \ + "act = \"%s\"\n", \ + exp_log, (char *)global_log.dat); \ + test_assert(0); \ + } \ } while (0) -#define _STR(x) #x -#define STR(x) _STR(x) - /* Actual tests ***************************************************************/ static_assert(sizeof(char) == 1); int main() { +#ifndef NDEBUG test_should_succeed(assert(true)); - test_should_fail(assert(false), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"false\" failed\n"); + 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__":"STR(__LINE__)":main(): assertion \"false\" failed: foo\n"); + 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__":"STR(__LINE__)":main(): assertion \"false\" failed\n"); + 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__":"STR(__LINE__)":main(): assertion \"notreached\" failed: xxx\n"); + test_should_fail(assert_notreached("xxx"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"notreached\" failed: xxx\n"); +#endif - if (global_log) { - free(global_log); - global_log = NULL; + if (global_log.dat) { + global_log_clear(); + free(global_log.dat); + global_log.dat = NULL; + global_log.cap = 0; } 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 index d0b547c..8c48727 100644 --- a/libmisc/tests/test_endian.c +++ b/libmisc/tests/test_endian.c @@ -1,6 +1,6 @@ /* libmisc/tests/test_endian.c - Tests for <libmisc/endian.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 */ @@ -11,22 +11,31 @@ #include "test.h" int main() { - uint8_t act[12] = {0}; - uint16be_encode(&act[0], UINT16_C(0x1234)); - uint32be_encode(&act[2], UINT32_C(0x56789ABC)); - uint16le_encode(&act[6], UINT16_C(0x1234)); - uint32le_encode(&act[8], UINT32_C(0x56789ABC)); + uint8_t act[(2+4+8)*2] = {}; + 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)); - uint8_t exp[12] = { 0x12, 0x34, - 0x56, 0x78, 0x9A, 0xBC, - 0x34, 0x12, - 0xBC, 0x9A, 0x78, 0x56 }; - test_assert(memcmp(act, exp, 12) == 0); + 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); - test_assert(uint16be_decode(&act[0]) == UINT16_C(0x1234)); - test_assert(uint32be_decode(&act[2]) == UINT32_C(0x56789ABC)); - test_assert(uint16le_decode(&act[6]) == UINT16_C(0x1234)); - test_assert(uint32le_decode(&act[8]) == UINT32_C(0x56789ABC)); + 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_fmt.c b/libmisc/tests/test_fmt.c new file mode 100644 index 0000000..64b3b8a --- /dev/null +++ b/libmisc/tests/test_fmt.c @@ -0,0 +1,243 @@ +/* libmisc/tests/test_fmt.c - Tests for <libmisc/fmt.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdlib.h> /* for free() */ +#include <string.h> /* for strcmp(), memcmp(), memset() */ + +#include <libmisc/fmt.h> + +#include "test.h" + +int main() { + char str[128] = {}; +#define do_print(...) fmt_snprint(str, sizeof(str), __VA_ARGS__) + + do_print("hello ", 9, " world!\n"); + test_assert(strcmp(str, "hello 9 world!\n") == 0); + memset(str, 0, sizeof(str)); + + do_print("hello ", (base8, 9), " world!\n"); + test_assert(strcmp(str, "hello 11 world!\n") == 0); + memset(str, 0, sizeof(str)); + + do_print("hello ", (base2, 9), (qstr, " world!\n")); + test_assert(strcmp(str, "hello 1001\" world!\\n\"") == 0); + memset(str, 0, sizeof(str)); + + do_print("hello ", (base16, 17), " world!\n"); + test_assert(strcmp(str, "hello 11 world!\n") == 0); + memset(str, 0, sizeof(str)); + + do_print((strn, "hello ", 4)); + test_assert(strcmp(str, "hell") == 0); + memset(str, 0, sizeof(str)); + + do_print((strn, "h\0ello ", 4)); + test_assert(memcmp(str, "h\0\0", 3) == 0); + memset(str, 0, sizeof(str)); + + do_print((mem, "hello ", 4)); + test_assert(strcmp(str, "hell") == 0); + memset(str, 0, sizeof(str)); + + do_print((mem, "hello\0world", strlen("hello world")+1)); + test_assert(memcmp(str, "hello\0world", strlen("hello world")+1) == 0); + memset(str, 0, sizeof(str)); + + do_print((qmem, "hello\0world", strlen("hello world")+1)); + test_assert(strcmp(str, "\"hello\\0world\\0\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstr, "hello\0world")); + test_assert(strcmp(str, "\"hello\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstrn, "hello\0world", strlen("hello world")+1)); + test_assert(strcmp(str, "\"hello\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstrn, "hello\0world", 4)); + test_assert(strcmp(str, "\"hell\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstr, "hello\xFFworld🚧")); + test_assert(strcmp(str, "\"hello\\xFFworld\\U0001F6A7\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstr, "¡hello world!")); + test_assert(strcmp(str, "\"\\u00A1hello world!\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qmem, "🚧", 3)); /* truncated UTF-8 */ + test_assert(strcmp(str, "\"\\xF0\\x9F\\x9A\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qmem, "\xF7\xBF\xBF\xBF", 4)); /* over unicode_max */ + test_assert(strcmp(str, "\"\\xF7\\xBF\\xBF\\xBF\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qmem, "\xE0\xA0", 2)); /* non-optimal encoding (of ' ') */ + test_assert(strcmp(str, "\"\\xE0\\xA0\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((byte, 'h'), (byte, 'w')); + test_assert(strcmp(str, "hw") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, 'h'), (qbyte, 'w')); + test_assert(strcmp(str, "'h''w'") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, 0)); + test_assert(strcmp(str, "'\\0'") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, '\\')); + test_assert(strcmp(str, "'\\\\'") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, '\'')); + test_assert(strcmp(str, "'\\''") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, '\n')); + test_assert(strcmp(str, "'\\n'") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, 0xff)); + test_assert(strcmp(str, "'\\xFF'") == 0); + memset(str, 0, sizeof(str)); + + do_print("zero ", 0); + test_assert(strcmp(str, "zero 0") == 0); + memset(str, 0, sizeof(str)); + + const char *const_str = "hello"; + do_print(const_str); + test_assert(strcmp(str, "hello") == 0); + memset(str, 0, sizeof(str)); + + bool t = true; + do_print(t); + test_assert(strcmp(str, "true") == 0); + memset(str, 0, sizeof(str)); + + bool f = false; + do_print(f); + test_assert(strcmp(str, "false") == 0); + memset(str, 0, sizeof(str)); + + /* Check that it accepts all primitive types of integer, not + * just all sizes of integer (e.g., on x86-64, + * sizeof(long)==sizeof(int), but they're different primitive + * types). */ + { + signed char x = 42; + do_print("schar ", x); + test_assert(strcmp(str, "schar 42") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned char x = 43; + do_print("uchar ", x); + test_assert(strcmp(str, "uchar 43") == 0); + memset(str, 0, sizeof(str)); + } + + { + short x = 44; + do_print("short ", x); + test_assert(strcmp(str, "short 44") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned short x = 45; + do_print("ushort ", x); + test_assert(strcmp(str, "ushort 45") == 0); + memset(str, 0, sizeof(str)); + } + + { + int x = 46; + do_print("int ", x); + test_assert(strcmp(str, "int 46") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned int x = 47; + do_print("uint ", x); + test_assert(strcmp(str, "uint 47") == 0); + memset(str, 0, sizeof(str)); + } + + { + long x = 48; + do_print("long ", x); + test_assert(strcmp(str, "long 48") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned long x = 49; + do_print("ulong ", x); + test_assert(strcmp(str, "ulong 49") == 0); + memset(str, 0, sizeof(str)); + } + + { + long long x = 50; + do_print("long long ", x); + test_assert(strcmp(str, "long long 50") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned long long x = 51; + do_print("ulong long ", x); + test_assert(strcmp(str, "ulong long 51") == 0); + memset(str, 0, sizeof(str)); + } + + do_print((ljust, 10, ' ', (base10, 1), "x")); + test_assert(strcmp(str, "1x ") == 0); + memset(str, 0, sizeof(str)); + + do_print((rjust, 10, ' ', (base10, 1), "x")); + test_assert(strcmp(str, " 1x") == 0); + memset(str, 0, sizeof(str)); + + do_print((base16_u8_, 1)); + test_assert(strcmp(str, "0x01") == 0); + memset(str, 0, sizeof(str)); + + do_print((base16_u16_, 1)); + test_assert(strcmp(str, "0x0001") == 0); + memset(str, 0, sizeof(str)); + + do_print((base16_u32_, 1)); + test_assert(strcmp(str, "0x00000001") == 0); + memset(str, 0, sizeof(str)); + + do_print((base16_u64_, 1)); + test_assert(strcmp(str, "0x0000000000000001") == 0); + memset(str, 0, sizeof(str)); + + do_print((hbyte, 1)); + test_assert(strcmp(str, "0x01") == 0); + memset(str, 0, sizeof(str)); + + do_print((hmem, "hello", 6)); + test_assert(strcmp(str, "{0x68,0x65,0x6C,0x6C,0x6F,0x00}") == 0); + memset(str, 0, sizeof(str)); + + char *astr = fmt_asprint(""); + test_assert(astr != NULL && astr[0] == '\0'); + free(astr); + + astr = fmt_asprint("hello ", (base2, 9), (qstr, " world!\n")); + test_assert(strcmp(astr, "hello 1001\" world!\\n\"") == 0); + free(astr); + + return 0; +} diff --git a/libmisc/tests/test_log.c b/libmisc/tests/test_log.c index 286738d..6e7cdfd 100644 --- a/libmisc/tests/test_log.c +++ b/libmisc/tests/test_log.c @@ -1,61 +1,80 @@ /* libmisc/tests/test_log.c - Tests for <libmisc/log.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 */ -#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() */ +#include <stdio.h> /* for vsnprintf() */ +#include <stdlib.h> /* for realloc(), free() */ +#include <string.h> /* for strlen(), strcmp() */ #define LOG_NAME FROBNICATE #include <libmisc/log.h> +#include <libmisc/_intercept.h> + #include "test.h" /* Intercept output ***********************************************************/ -static char *log_output = NULL; +static struct fmt_buf log_output = {}; + +void __lm_putchar(unsigned char c) { + if (log_output.len+1 >= log_output.cap) { + log_output.cap += 16; + log_output.dat = realloc(log_output.dat, log_output.cap); + memset(log_output.dat + log_output.len, 0, log_output.cap - log_output.len); + } + ((uint8_t *)log_output.dat)[log_output.len++] = (uint8_t)c; +} -int vprintf(const char *format, va_list ap) { - return vasprintf(&log_output, format, ap); +static void log_output_clear(void) { + if (log_output.dat) + memset(log_output.dat, 0, log_output.cap); + log_output.len = 0; } /* Actual tests ***************************************************************/ -#define should_print(_exp, cmd) do { \ - char *exp = _exp; \ - test_assert(!log_output); \ - cmd; \ - if (!exp) \ - test_assert(!log_output); \ - else \ - if (!(log_output != NULL && \ - strcmp(log_output, exp) == 0)) { \ - printf("exp = \"%s\"\n" \ - "act = \"%s\"\n", \ - exp, log_output); \ - test_assert(0); \ - } \ - if (log_output) { \ - free(log_output); \ - log_output = NULL; \ - } \ +#define should_print(_exp, cmd) do { \ + char *exp = _exp; \ + test_assert(log_output.len == 0); \ + cmd; \ + if (!exp) \ + test_assert(log_output.len == 0); \ + else { \ + test_assert(log_output.dat); \ + test_assert(strlen(log_output.dat) == log_output.len); \ + if (strcmp(log_output.dat, exp)) { \ + printf("exp = \"%s\"\n" \ + "act = \"%s\"\n", \ + exp, (char *)log_output.dat); \ + test_assert(0); \ + } \ + } \ + log_output_clear(); \ } while (0) int main() { should_print("error: FROBNICATE: val=42\n", - errorf("val=%d", 42)); + log_errorln("val=", 42)); should_print("info : FROBNICATE: val=0\n", - infof("val=%d", 0)); + log_infoln("val=", 0)); +#ifndef NDEBUG #define CONFIG_FROBNICATE_DEBUG 1 should_print("debug: FROBNICATE: val=-2\n", - debugf("val=%d", -2)); + log_debugln("val=", -2)); #undef CONFIG_FROBNICATE_DEBUG #define CONFIG_FROBNICATE_DEBUG 0 should_print(NULL, - debugf("val=%d", -2)); + log_debugln("val=", -2)); +#undef CONFIG_FROBNICATE_DEBUG +#endif + + if (log_output.dat) { + free(log_output.dat); + log_output.dat = NULL; + log_output.cap = 0; + } return 0; } diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c new file mode 100644 index 0000000..6810005 --- /dev/null +++ b/libmisc/tests/test_macro.c @@ -0,0 +1,191 @@ +/* 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 <stdlib.h> /* for free() */ +#include <string.h> /* for strcmp(), strlen(), memcmp(), strdup() */ + +#include <libmisc/macro.h> + +#include "test.h" + +/** Given `N` from `#define _LM_EVAL _LM_EVAL__{N}`, UNDER is `(N*2)-2`. (16*2)-2=30. */ +#define UNDER 30 +/** Given `N` from `#define _LM_EVAL _LM_EVAL__{N}`, OVER is `(N*2)-1`. (16*2)-1=31. */ +#define OVER 31 + +/** XUNDER is 0 through `UNDER` inclusive. */ +#define XUNDER \ + X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15) \ + X(16) X(17) X(18) X(19) X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) X(28) X(29) X(30) +/** XUNDER is 0 through `OVER` inclusive. */ +#define XOVER XUNDER X(OVER) + +static char *without_spaces(const char *in) { + char *out = strdup(in); + for (size_t i = 0; out[i]; i++) + while (out[i] == ' ') + for (size_t j = i; out[j]; j++) + out[j] = out[j+1]; + return out; +} + +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); + + printf("== LM_TUPLE ===============================================\n"); + test_assert(LM_IF(LM_IS_TUPLE( 9 ))(0)(1)); + test_assert(LM_IF(LM_IS_TUPLE( a ))(0)(1)); + test_assert(LM_IF(LM_IS_TUPLE( () ))(1)(0)); + test_assert(LM_IF(LM_IS_TUPLE( (9) ))(1)(0)); + test_assert(LM_IF(LM_IS_TUPLE( (a) ))(1)(0)); + test_assert(LM_IF(LM_IS_TUPLE( (a, b) ))(1)(0)); + + test_assert(LM_IF(LM_IS_EMPTY_TUPLE( () ))(1)(0)); + test_assert(LM_IF(LM_IS_EMPTY_TUPLE( 9 ))(0)(1)); + test_assert(LM_IF(LM_IS_EMPTY_TUPLE( a ))(0)(1)); + test_assert(LM_IF(LM_IS_EMPTY_TUPLE( (9) ))(0)(1)); + test_assert(LM_IF(LM_IS_EMPTY_TUPLE( (a) ))(0)(1)); + test_assert(LM_IF(LM_IS_EMPTY_TUPLE( (a, b) ))(0)(1)); + + printf("== LM_TUPLES ==============================================\n"); + test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( ))(0)(1)); + test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( () ))(1)(0)); + test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( (a) ))(1)(0)); + test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( (a)(b) ))(1)(0)); + test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( (a)(b)(c) ))(1)(0)); + + printf("== LM_FOREACH_PARAM =======================================\n"); + /* Basic test. */ + { + #define FN(A, B) A "-" #B + const char *str = LM_FOREACH_PARAM(FN, (" "), a, (b), c); + #undef FN + test_assert(strcmp(str, " -a -(b) -c") == 0); + } + + /* Test that it works with the documented limit of params. */ + { + #define X(n) , n + #define FN(n) #n "\n" + const char *str = LM_FOREACH_PARAM_(FN, () XUNDER); + #undef FN + #undef X + #define X(n) #n "\n" + test_assert(strcmp(str, XUNDER) == 0); + #undef X + } + + /* Test that it breaks at documented_limit+1 tuples. */ + { + #define X(n) , n + #define FN(n) n + const char *str = LM_STR_(LM_FOREACH_PARAM_(FN, () XOVER)); + #undef FN + #undef X + /* This comparison is a little extra complicated in + * order to not be sensitive to whitespace in the + * suffix. */ + #define X(n) #n " " + const char *exp_prefix = XUNDER; + #undef X + const char *exp_suffix = "FN(" LM_STR_(OVER) ")_LM_FOREACH_PARAM_ITEM_indirect()(FN,(),())"; + test_assert(strlen(exp_prefix) < strlen(str) && memcmp(exp_prefix, str, strlen(exp_prefix)) == 0); + char *act_suffix = without_spaces(&str[strlen(exp_prefix)]); + test_assert(strcmp(act_suffix, exp_suffix) == 0); + free(act_suffix); + } + + printf("== LM_FOREACH_TUPLE =======================================\n"); + /* Basic test. */ + { + #define FN(a, b) a "-" b + const char *str = LM_FOREACH_TUPLE( ("a") ("b") ("c"), FN, " "); + #undef FN + test_assert(strcmp(str, " -a -b -c") == 0); + } + + /* Test that it works with the documented limit of tuples. */ + { + #define X(n) (n) + #define FN(n) #n "\n" + const char *str = LM_FOREACH_TUPLE(XUNDER, FN); + #undef FN + #undef X + #define X(n) #n "\n" + test_assert(strcmp(str, XUNDER) == 0); + #undef X + } + + /* Test that it breaks at documented_limit+1 tuples. */ + { + #define X(n) (n) + #define FN(n) n + const char *str = LM_STR_(LM_FOREACH_TUPLE(XOVER, FN)); + #undef FN + #undef X + /* This comparison is a little extra complicated in + * order to not be sensitive to whitespace in the + * suffix. */ + #define X(n) #n " " + const char *exp_prefix = XUNDER; + #undef X + const char *exp_suffix = "FN(" LM_STR_(OVER) ")_LM_FOREACH_TUPLE_indirect()(,FN,)"; + test_assert(strlen(exp_prefix) < strlen(str) && memcmp(exp_prefix, str, strlen(exp_prefix)) == 0); + char *act_suffix = without_spaces(&str[strlen(exp_prefix)]); + test_assert(strcmp(act_suffix, exp_suffix) == 0); + free(act_suffix); + } + + printf("== LM_DEFAPPEND ===========================================\n"); + LM_DEFAPPEND(mylist, a); + LM_DEFAPPEND(mylist, + b); + { + const char *str = LM_STR_(mylist); + test_assert(strcmp(str, "a b") == 0); + } + + return 0; +} 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_obj.c b/libmisc/tests/test_obj.c new file mode 100644 index 0000000..a13b8c9 --- /dev/null +++ b/libmisc/tests/test_obj.c @@ -0,0 +1,61 @@ +/* libmisc/tests/test_obj.c - Tests for <libmisc/obj.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/obj.h> + +#include "test.h" + +/* `lo_inteface frobber` header ***********************************************/ + +#define frobber_LO_IFACE \ + /** Basic function. */ \ + LO_FUNC(int, frob) \ + /** Function that takes 1 argument. */ \ + LO_FUNC(int, frob1, int) \ + /** Function that returns nothing. */ \ + LO_FUNC(void, frob0) +LO_INTERFACE(frobber); + +/* `struct myclass` header ****************************************************/ + +struct myclass { + int a; +}; +LO_IMPLEMENTATION_H(frobber, struct myclass, myclass); + +/* `struct myclass` implementation ********************************************/ + +LO_IMPLEMENTATION_C(frobber, struct myclass, myclass, static); + +static int myclass_frob(struct myclass *self) { + test_assert(self); + return self->a; +} + +static int myclass_frob1(struct myclass *self, int arg) { + test_assert(self); + return arg; +} + +static void myclass_frob0(struct myclass *self) { + test_assert(self); +} + +/* main test body *************************************************************/ + +#define MAGIC1 909837 +#define MAGIC2 657441 + +int main() { + struct myclass obj = { + .a = MAGIC1, + }; + lo_interface frobber iface = LO_BOX(frobber, &obj); + test_assert(LO_CALL(iface, frob) == MAGIC1); + test_assert(LO_CALL(iface, frob1, MAGIC2) == MAGIC2); + LO_CALL(iface, frob0); + return 0; +} diff --git a/libmisc/tests/test_obj_nest.c b/libmisc/tests/test_obj_nest.c new file mode 100644 index 0000000..ba5ac37 --- /dev/null +++ b/libmisc/tests/test_obj_nest.c @@ -0,0 +1,73 @@ +/* libmisc/tests/test_obj_nest.c - Tests for <libmisc/obj.h> nesting + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcpy() */ + +#include <libmisc/obj.h> + +#include "test.h" + +/* interfaces *****************************************************************/ + +#define reader_LO_IFACE \ + LO_FUNC(ssize_t, read, void *, size_t) +LO_INTERFACE(reader); + +#define writer_LO_IFACE \ + LO_FUNC(ssize_t, write, void *, size_t) +LO_INTERFACE(writer); + +#define read_writer_LO_IFACE \ + LO_NEST(reader) \ + LO_NEST(writer) +LO_INTERFACE(read_writer); + +/* implementation header ******************************************************/ + +struct myclass { + size_t len; + char buf[512]; +}; +LO_IMPLEMENTATION_H(reader, struct myclass, myclass); +LO_IMPLEMENTATION_H(writer, struct myclass, myclass); +LO_IMPLEMENTATION_H(read_writer, struct myclass, myclass); + +/* implementation main ********************************************************/ + +LO_IMPLEMENTATION_C(reader, struct myclass, myclass, static); +LO_IMPLEMENTATION_C(writer, struct myclass, myclass, static); +LO_IMPLEMENTATION_C(read_writer, struct myclass, myclass, static); + +static ssize_t myclass_read(struct myclass *self, void *buf, size_t count) { + test_assert(self); + if (count > self->len) + count = self->len; + memcpy(buf, self->buf, count); + return count; +} + +static ssize_t myclass_write(struct myclass *self, void *buf, size_t count) { + test_assert(self); + if (self->len) + return -1; + if (count > sizeof(self->buf)) + count = sizeof(self->buf); + memcpy(self->buf, buf, count); + self->len = count; + return count; +} + +/* main test body *************************************************************/ + +int main() { + struct myclass _obj = {}; + lo_interface read_writer obj = LO_BOX(read_writer, &_obj); + test_assert(LO_CALL(obj, write, "Hello", 6) == 6); + char buf[6] = {}; + test_assert(LO_CALL(obj, read, buf, 3) == 3); + test_assert(memcmp(buf, "Hel\0\0\0", 6) == 0); + return 0; +} diff --git a/libmisc/tests/test_private.c b/libmisc/tests/test_private.c index 7aaf1ee..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 */ @@ -8,18 +8,18 @@ struct a { int foo; - BEGIN_PRIVATE(A) + BEGIN_PRIVATE(A); int bar; - END_PRIVATE(A) + END_PRIVATE(A); }; #define IMPLEMENTATION_FOR_B YES struct b { int foo; - BEGIN_PRIVATE(B) + BEGIN_PRIVATE(B); int bar; - END_PRIVATE(B) + END_PRIVATE(B); }; int main() { diff --git a/libmisc/tests/test_rand.c b/libmisc/tests/test_rand.c index fff1b27..1cfbd65 100644 --- a/libmisc/tests/test_rand.c +++ b/libmisc/tests/test_rand.c @@ -1,33 +1,24 @@ /* libmisc/tests/test_rand.c - Tests for <libmisc/rand.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 */ -#include <stdbool.h> #include <setjmp.h> +#include <libmisc/_intercept.h> #include <libmisc/rand.h> #include "test.h" /* Intercept failures *********************************************************/ +#ifndef NDEBUG jmp_buf *__catch; -void __assert_msg_fail(const char *expr, - const char *file, unsigned int line, const char *func, - const char *msg) { - static bool in_fail = false; +void __lm_abort(void) { if (__catch) longjmp(*__catch, 1); - if (!in_fail) { - in_fail = true; - printf("error: %s:%u:%s(): assertion \"%s\" failed%s%s\n", - file, line, func, - expr, - msg ? ": " : "", msg); - } abort(); } @@ -43,6 +34,7 @@ void __assert_msg_fail(const char *expr, __catch = old_catch; \ } \ } while (0); +#endif /* Actual tests ***************************************************************/ @@ -51,20 +43,24 @@ void __assert_msg_fail(const char *expr, 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}; + bool seen[MAX_SEE_ALL] = {}; for (int i = 0; i < ROUNDS; i++) { uint64_t val = rand_uint63n(cnt); - sum += ((double)val)/(cnt-1); + sum += val; test_assert(val < cnt); if (cnt < MAX_SEE_ALL) seen[val] = true; } if (cnt > 1) { - test_assert(sum/ROUNDS > 0.45); - test_assert(sum/ROUNDS < 0.55); + 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++) diff --git a/libmisc/tests/test_vcall.c b/libmisc/tests/test_vcall.c deleted file mode 100644 index f36fc4b..0000000 --- a/libmisc/tests/test_vcall.c +++ /dev/null @@ -1,74 +0,0 @@ -/* libmisc/tests/test_vcall.c - Tests for <libmisc/vcall.h> - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <libmisc/assert.h> -#include <libmisc/vcall.h> - -#include "test.h" - -/******************************************************************************/ - -struct frobber_vtable; - -typedef struct { - struct frobber_vtable *vtable; -} implements_frobber; - -struct frobber_vtable { - int (*frob)(implements_frobber *); - int (*frob1)(implements_frobber *, int); - void (*frob0)(implements_frobber *); -}; - -/******************************************************************************/ - -struct myclass { - int a; - implements_frobber; -}; -static_assert(offsetof(struct myclass, implements_frobber) != 0); - -static int myclass_frob(implements_frobber *_self) { - struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self); - test_assert(self); - test_assert((void*)self != (void*)_self); - return self->a; -} - -static int myclass_frob1(implements_frobber *_self, int arg) { - struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self); - test_assert(self); - test_assert((void*)self != (void*)_self); - return arg; -} - -static void myclass_frob0(implements_frobber *_self) { - struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self); - test_assert(self); - test_assert((void*)self != (void*)_self); -} - -struct frobber_vtable myclass_vtable = { - .frob = myclass_frob, - .frob1 = myclass_frob1, - .frob0 = myclass_frob0, -}; - -/******************************************************************************/ - -#define MAGIC1 909837 -#define MAGIC2 657441 - -int main() { - struct myclass obj = { - .implements_frobber = { .vtable = &myclass_vtable }, - .a = MAGIC1, - }; - test_assert(VCALL(&obj, frob) == MAGIC1); - test_assert(VCALL(&obj, frob1, MAGIC2) == MAGIC2); - VCALL(&obj, frob0); - return 0; -} diff --git a/libmisc/utf8.c b/libmisc/utf8.c new file mode 100644 index 0000000..28357f0 --- /dev/null +++ b/libmisc/utf8.c @@ -0,0 +1,44 @@ +/* libmisc/utf8.c - UTF-8 routines + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/utf8.h> + +void utf8_decode_codepoint(const uint8_t *str, size_t len, uint32_t *ret_ch, uint8_t *ret_chlen) { + uint32_t ch; + uint8_t chlen; + uint32_t chmin; + if ((str[0] & 0b10000000) == 0b00000000) { ch = str[0] & 0b01111111; chlen = 1; chmin = 0; } /* bits=7+(0*6)= 7 */ + else if ((str[0] & 0b11100000) == 0b11000000) { ch = str[0] & 0b00011111; chlen = 2; chmin = UINT32_C(1)<< 7; } /* bits=5+(1*6)=11 */ + else if ((str[0] & 0b11110000) == 0b11100000) { ch = str[0] & 0b00001111; chlen = 3; chmin = UINT32_C(1)<<11; } /* bits=4+(2*6)=16 */ + else if ((str[0] & 0b11111000) == 0b11110000) { ch = str[0] & 0b00000111; chlen = 4; chmin = UINT32_C(1)<<16; } /* bits=3+(3*6)=21 */ + else goto invalid; + if (chlen > len) + goto invalid; + for (uint8_t i = 1; i < chlen; i++) { + if ((str[i] & 0b11000000) != 0b10000000) + goto invalid; + ch = (ch << 6) | (str[i] & 0b00111111); + } + if (ch > 0x10FFFF || ch < chmin) + goto invalid; + *ret_ch = ch; + *ret_chlen = chlen; + return; + invalid: + *ret_chlen = 0; +} + +bool _utf8_is_valid(const uint8_t *str, size_t len, bool forbid_nul) { + for (size_t pos = 0; pos < len;) { + uint32_t ch; + uint8_t chlen; + utf8_decode_codepoint(&str[pos], len-pos, &ch, &chlen); + if (chlen == 0 || (forbid_nul && ch == 0)) + return false; + pos += chlen; + } + return true; +} diff --git a/libmisc/wrap-cc b/libmisc/wrap-cc new file mode 100755 index 0000000..e7a0b91 --- /dev/null +++ b/libmisc/wrap-cc @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# libmisc/wrap-cc - Wrapper around GCC to enhance the preprocessor +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import os +import subprocess +import sys +import typing + + +def scan_tuple( + text: str, beg: int, on_part: typing.Callable[[str], None] | None = None +) -> int: + assert text[beg] == "(" + pos = beg + 1 + arg_start = pos + parens = 1 + instring = False + while parens: + c = text[pos] + if instring: + match c: + case "\\": + pos += 1 + case '"': + instring = False + else: + match c: + case "(": + parens += 1 + case ")": + parens -= 1 + if on_part and parens == 0 and text[beg + 1 : pos].strip(): + on_part(text[arg_start:pos]) + case ",": + if on_part and parens == 1: + on_part(text[arg_start:pos]) + arg_start = pos + 1 + case '"': + instring = True + pos += 1 + assert text[pos - 1] == ")" + return pos - 1 + + +def unquote(cstr: str) -> str: + assert len(cstr) >= 2 and cstr[0] == '"' and cstr[-1] == '"' + cstr = cstr[1:-1] + out = "" + while cstr: + if cstr[0] == "\\": + match cstr[1]: + case "n": + out += "\n" + cstr = cstr[2:] + case "\\": + out += "\\" + cstr = cstr[2:] + case '"': + out += '"' + cstr = cstr[2:] + else: + out += cstr[0] + cstr = cstr[1:] + return out + + +def preprocess(all_args: list[str]) -> typing.NoReturn: + # argparse ################################################################# + _args = all_args + + def shift(n: int) -> list[str]: + nonlocal _args + ret = _args[:n] + _args = _args[n:] + return ret + + arg0 = shift(1)[0] + common_flags: list[str] = [] + output_flags: list[str] = [] + positional: list[str] = [] + while _args: + if len(_args[0]) > 2 and _args[0][0] == "-" and _args[0][1] in "IDU": + _args = [_args[0][:2], _args[0][2:], *_args[1:]] + match _args[0]: + # Mode + case "-E" | "-quiet": + common_flags += shift(1) + case "-lang-asm": + os.execvp(all_args[0], all_args) + # Search path + case "-I" | "-imultilib" | "-isystem": + common_flags += shift(2) + # Define/Undefine + case "-D" | "-U": + common_flags += shift(2) + # Optimization + case "-O0" | "-O1" | "-O2" | "-O3" | "-Os" | "-Ofast" | "-Og" | "-Oz": + common_flags += shift(1) + case "-g": + common_flags += shift(1) + # Output files + case "-MD" | "-MF" | "-MT" | "-dumpbase" | "-dumpbase-ext": + output_flags += shift(2) + case "-o": + output_flags += shift(2) + # Other + case _: + if _args[0].startswith("-"): + if _args[0].startswith("-std="): + common_flags += shift(1) + elif _args[0].startswith("-m"): + common_flags += shift(1) + elif _args[0].startswith("-f"): + common_flags += shift(1) + elif _args[0].startswith("-W"): + common_flags += shift(1) + else: + raise ValueError(f"unknown flag: {_args!r}") + else: + positional += shift(1) + if len(positional) != 1: + raise ValueError("expected 1 input file") + infile = positional[0] + + # enhance ################################################################## + + common_flags += ["-D", "__LIBMISC_ENHANCED_CPP__"] + + text = subprocess.run( + [arg0, *common_flags, infile], + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=sys.stderr, + check=True, + text=True, + ).stdout + + macros: dict[str, str] = {} + + marker = "__xx__LM_DEFAPPEND__xx__" + pos = 0 + while (marker_beg := text.find(marker, pos)) >= 0: + args: list[str] = [] + + def add_arg(arg: str) -> None: + nonlocal args + args.append(arg) + + beg_paren = marker_beg + len(marker) + end_paren = scan_tuple(text, beg_paren, add_arg) + + before = text[:marker_beg] + # old = text[marker_beg : end_paren + 1] + after = text[end_paren + 1 :] + + assert len(args) == 2 + k = unquote(args[0].strip()) + v = unquote(args[1].strip()) + if k not in macros: + macros[k] = v + else: + macros[k] += " " + v + + text = before + after + pos = len(before) + + common_flags += ["-D", marker + "=LM_EAT"] + for k, v in macros.items(): + common_flags += ["-D", k + "=" + v] + + # Run, for-real ############################################################ + os.execvp(arg0, [arg0, *common_flags, *output_flags, infile]) + + +def main(all_args: list[str]) -> typing.NoReturn: + if len(all_args) >= 2 and all_args[0].endswith("cc1") and all_args[1] == "-E": + preprocess(all_args) + else: + os.execvp(all_args[0], all_args) + + +if __name__ == "__main__": + main(sys.argv[1:]) |