#ifndef _AKSTDLIB_H_ #define _AKSTDLIB_H_ #include /* * libakerror 1.0.0 is the floor. That release moved the status-name table into a * private registry -- AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, the * registry entry points raise akerr_ErrorContext * instead of returning int, and * the library gained an soname -- so a translation unit that pairs this header * with a pre-1.0.0 akerror.h is an ABI mismatch, not just a compile problem. * * libakerror publishes no version macro, so this feature-tests on * AKERR_FIRST_CONSUMER_STATUS, which that release introduced, rather than on a * version number that does not exist. Without the guard a stale installed header * fails much further in, as a pile of unrelated errors inside src/stdlib.c. * * See deps/libakerror/UPGRADING.md. */ #ifndef AKERR_FIRST_CONSUMER_STATUS #error "libakstdlib requires libakerror >= 1.0.0: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror." #endif /* * AKSL_VERSION_MAJOR/MINOR/PATCH/STRING/NUMBER and AKSL_VERSION_SONAME. * Generated by CMake from include/akstdlib_version.h.in, which is why there is * no such file in the source tree -- it is configured into the build tree and * installed alongside this header. */ #include /* * What this header needs in its own declarations, and no more: * stdio.h FILE * stddef.h size_t * stdint.h uint32_t * stdarg.h va_list, for the v* forms of the formatted-output wrappers * * It used to pull in stdlib.h and string.h as well, which nothing here needs and * which every consumer then got whether it wanted them or not. stddef.h in place * of stdlib.h is the same size_t at a fraction of the namespace. TODO.md 2.2.16. */ #include #include #include #include /* off_t, for the aksl_fseeko/aksl_ftello pair. POSIX, like aksl_realpath. */ #include #ifdef __cplusplus extern "C" { #endif /* * Restore the compile-time format/argument checking that callers otherwise lose * by going through a variadic wrapper: without it, printf("%d", "str") is caught * and aksl_printf(&n, "%d", "str") is not. TODO.md 2.2.5. */ #if defined(__GNUC__) || defined(__clang__) #define AKSL_PRINTF_FORMAT(__fmt_index, __first_arg) \ __attribute__((format(printf, __fmt_index, __first_arg))) #define AKSL_SCANF_FORMAT(__fmt_index, __first_arg) \ __attribute__((format(scanf, __fmt_index, __first_arg))) #else #define AKSL_PRINTF_FORMAT(__fmt_index, __first_arg) #define AKSL_SCANF_FORMAT(__fmt_index, __first_arg) #endif typedef struct aksl_ListNode { void *data; struct aksl_ListNode *next; struct aksl_ListNode *prev; } aksl_ListNode; typedef struct aksl_TreeNode { struct aksl_TreeNode *parent; struct aksl_TreeNode *left; struct aksl_TreeNode *right; void *leaf; } aksl_TreeNode; #define AKSL_TREE_SEARCH_BFS 0 /** Breadth-first (left child before right) search mode for tree nodes */ #define AKSL_TREE_SEARCH_BFS_RIGHT 1 /** Breadth-first, right child before left, search mode for tree nodes */ #define AKSL_TREE_SEARCH_DFS 2 /** Alias for AKSL_TREE_SEARCH_DFS_PREORDER */ #define AKSL_TREE_SEARCH_DFS_PREORDER 2 /** Depth first pre-order (root, left, right) search mode for tree nodes */ #define AKSL_TREE_SEARCH_DFS_INORDER 3 /** Depth first in-order (left, root, right) search mode for tree nodes */ #define AKSL_TREE_SEARCH_DFS_POSTORDER 4 /** Depth first post-order (left, right, root) search mode for tree nodes */ #define AKSL_TREE_SEARCH_VISIT 5 /** Visit the node and stop; do not traverse the children */ /* * How deep aksl_tree_iterate will walk before raising AKERR_OUTOFBOUNDS. * * This is a real limit, not a formality: the depth-first walk is recursive, so * without it a degenerate chain overflows the stack and a child pointing back at * an ancestor recurses until the process dies. 256 is far past any balanced tree * that fits in memory (2^256 nodes) and comfortably short of a stack overflow, * so in practice it only ever rejects the degenerate shapes it exists to reject. * * It also sizes the ancestor buffer aksl_tree_iterate keeps on its own stack -- * one pointer per level, so raising it costs 8 bytes of stack per level. */ #define AKSL_TREE_MAX_DEPTH 256 /* * Head/tail/length container. aksl_list_append has to walk the list to find its * tail, so building n nodes with it is O(n^2); the container makes that O(1) and * makes the length free. It owns no memory -- the nodes are still the caller's. */ typedef struct aksl_List { aksl_ListNode *head; aksl_ListNode *tail; size_t length; } aksl_List; typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_ListNodeIterator)(aksl_ListNode *node, void *data); typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_TreeNodeIterator)(aksl_TreeNode *node, void *data); typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_AllocFunc)(size_t size, void **dest); typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_FreeFunc)(void *ptr); /* Reports its answer through *matched (non-zero to accept) and may raise. */ typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_ListNodePredicate)(aksl_ListNode *node, void *data, int *matched); /* Negative, zero or positive through *dest, as strcmp(3) has it. */ typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_TreeCompareFunc)(void *a, void *b, int *dest); typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_HashMapIterator)(const char *key, void *value, void *data); /* * Version of the shared library actually loaded, as opposed to the * AKSL_VERSION_* macros above, which record what the caller was *compiled* * against. The two differ exactly when a stale libakstdlib.so is on the loader * path, which is the failure these entry points exist to name. * * All three pointers are required; this library treats a NULL out-param as a * caller error rather than as "don't care" (so does aksl_free). */ akerr_ErrorContext AKERR_NOIGNORE *aksl_version(int *major, int *minor, int *patch); /* * The loaded library's version as "MAJOR.MINOR.PATCH", and its soname. Neither * can fail -- they return pointers to string literals in the library's own * .rodata -- so they return the string directly rather than an error context, * the same exception akerr_name_for_status() makes for the same reason. */ const char *aksl_version_string(void); const char *aksl_version_soname(void); /* * Compare the caller's compiled-in version against the loaded library's. * Compatibility is defined as "same soname", so pre-1.0 both major and minor * must match and patch is ignored; from 1.0 only major will matter. Raises * AKERR_VALUE naming both versions on a mismatch. * * Call it through AKSL_VERSION_CHECK() below rather than directly. The macro * expands at *your* call site, so it captures the AKSL_VERSION_* the caller was * built with; the function compares them against the values baked into the * library. Passing the numbers by hand defeats the entire mechanism. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_version_check(int major, int minor, int patch); #define AKSL_VERSION_CHECK() \ aksl_version_check(AKSL_VERSION_MAJOR, AKSL_VERSION_MINOR, AKSL_VERSION_PATCH) /* * Stream I/O. * * fread and fwrite take a required size_t *nmemb_out. It is always written -- * including on the failure paths -- so a caller who gets AKERR_EOF can find out * how much data arrived before the stream ran out, which is the whole point of * distinguishing EOF from an error. A transfer that comes up short with no * stream error set is AKERR_IO, not silent success. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(const char *pathname, const char *mode, FILE **fp); akerr_ErrorContext AKERR_NOIGNORE *aksl_fread(void *ptr, size_t size, size_t nmemb, FILE *stream, size_t *nmemb_out); akerr_ErrorContext AKERR_NOIGNORE *aksl_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp, size_t *nmemb_out); akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream); /* * Memory. aksl_malloc and aksl_calloc reject a zero-size request outright rather * than reporting whatever errno happened to hold when malloc(0) returned NULL. * aksl_realloc takes the pointer by reference and leaves it valid and untouched * on failure, which is realloc(3)'s classic leak closed off. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_malloc(size_t size, void **dst); akerr_ErrorContext AKERR_NOIGNORE *aksl_calloc(size_t nmemb, size_t size, void **dst); akerr_ErrorContext AKERR_NOIGNORE *aksl_realloc(void **ptr, size_t size); akerr_ErrorContext AKERR_NOIGNORE *aksl_free(void *ptr); akerr_ErrorContext AKERR_NOIGNORE *aksl_freep(void **ptr); akerr_ErrorContext AKERR_NOIGNORE *aksl_memset(void *s, int c, size_t n); /* Overlapping ranges are AKERR_VALUE, not undefined behaviour; use aksl_memmove. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_memcpy(void *d, const void *s, size_t n); akerr_ErrorContext AKERR_NOIGNORE *aksl_memmove(void *d, const void *s, size_t n); akerr_ErrorContext AKERR_NOIGNORE *aksl_memcmp(const void *a, const void *b, size_t n, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_memchr(const void *s, int c, size_t n, void **dest); /* * Formatted output. *count is the byte count written excluding the terminating * NUL, and is 0 on every failure path. * * There is no aksl_sprintf. It wrapped vsprintf, which cannot be bounded, and an * error-handling wrapper around an unbounded write is the sharp edge this * library exists to remove. aksl_snprintf replaces it and treats truncation as * AKERR_OUTOFBOUNDS rather than as a short success. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_printf(int *count, const char *restrict format, ...) AKSL_PRINTF_FORMAT(2, 3); akerr_ErrorContext AKERR_NOIGNORE *aksl_fprintf(int *count, FILE *restrict stream, const char *restrict format, ...) AKSL_PRINTF_FORMAT(3, 4); akerr_ErrorContext AKERR_NOIGNORE *aksl_snprintf(int *count, char *restrict str, size_t size, const char *restrict format, ...) AKSL_PRINTF_FORMAT(4, 5); akerr_ErrorContext AKERR_NOIGNORE *aksl_vprintf(int *count, const char *restrict format, va_list args); akerr_ErrorContext AKERR_NOIGNORE *aksl_vfprintf(int *count, FILE *restrict stream, const char *restrict format, va_list args); akerr_ErrorContext AKERR_NOIGNORE *aksl_vsnprintf(int *count, char *restrict str, size_t size, const char *restrict format, va_list args); /* * String to number. * * These are strict, unlike the libc functions they are named for: * * no digits consumed -> AKERR_VALUE * trailing junk, endptr NULL -> AKERR_VALUE * value outside the type -> ERANGE * * and *dest is 0 on every failure. Pass a non-NULL endptr to opt out of the * trailing-junk check and read a number off the front of a longer string. * * The ato* forms are aksl_strto* with base 10 and a NULL endptr, so the whole * string must be a number. "0x10" is AKERR_VALUE through aksl_atoi -- base 10 * stops at the 'x' -- and 16 through aksl_strtol(nptr, NULL, 0, &dest). */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strtol(const char *nptr, char **endptr, int base, long *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strtoll(const char *nptr, char **endptr, int base, long long *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strtoul(const char *nptr, char **endptr, int base, unsigned long *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strtoull(const char *nptr, char **endptr, int base, unsigned long long *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strtod(const char *nptr, char **endptr, double *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strtof(const char *nptr, char **endptr, float *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strtold(const char *nptr, char **endptr, long double *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_atoi(const char *nptr, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_atol(const char *nptr, long *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_atoll(const char *nptr, long long *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_atof(const char *nptr, double *dest); /* * realpath(3). `buflen` must be at least PATH_MAX, because realpath(3) offers no * way to bound its own write -- an undersized buffer is AKERR_OUTOFBOUNDS rather * than a buffer overflow. aksl_realpath_alloc sizes and allocates the result * instead; *dest is then the caller's to release with aksl_free. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_realpath(const char *restrict path, char *restrict resolved_path, size_t buflen); akerr_ErrorContext AKERR_NOIGNORE *aksl_realpath_alloc(const char *restrict path, char **dest); /* * djb2. Length-driven, so embedded NUL bytes hash like any other byte; bytes * >= 0x80 are read as unsigned, so the result matches canonical djb2 and does * not depend on whether plain char is signed on the target. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len, uint32_t *hashval); akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32_t *hashval); /* ---------------------------------------------------------------------- */ /* Strings -- src/string.c */ /* ---------------------------------------------------------------------- */ /* * Two conventions run through this whole section. * * The copying and concatenating functions take the size of the destination * buffer, even the ones named after libc functions that do not. strcpy(3) and * strcat(3) cannot be called safely without knowing how much room there is, so * a wrapper that took the same arguments would be an error-handling wrapper * around a buffer overflow. dstsize is the whole buffer including the * terminator -- pair `char buf[64]` with `sizeof(buf)`. Truncation is * AKERR_OUTOFBOUNDS, and nothing is written when it happens, so ignoring the * status leaves an empty string rather than a plausible-looking prefix. * * The searching functions answer through an out-param, and finding nothing is a * successful answer of NULL rather than an error. The result points into the * caller's own string and must not be freed. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strlen(const char *s, size_t *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strnlen(const char *s, size_t maxlen, size_t *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strcpy(char *dst, size_t dstsize, const char *src); /* Always terminates, and never NUL-pads to n; strncpy(3) does both the other way. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strncpy(char *dst, size_t dstsize, const char *src, size_t n); akerr_ErrorContext AKERR_NOIGNORE *aksl_strcat(char *dst, size_t dstsize, const char *src); akerr_ErrorContext AKERR_NOIGNORE *aksl_strncat(char *dst, size_t dstsize, const char *src, size_t n); /* *dest is the caller's, to release with aksl_free or aksl_freep. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strdup(const char *s, char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strndup(const char *s, size_t n, char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strcmp(const char *a, const char *b, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strncmp(const char *a, const char *b, size_t n, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strcasecmp(const char *a, const char *b, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strncasecmp(const char *a, const char *b, size_t n, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strcoll(const char *a, const char *b, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strchr(const char *s, int c, char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strrchr(const char *s, int c, char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strstr(const char *haystack, const char *needle, char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strcasestr(const char *haystack, const char *needle, char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strpbrk(const char *s, const char *accept, char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strspn(const char *s, const char *accept, size_t *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strcspn(const char *s, const char *reject, size_t *dest); /* * The reentrant tokeniser only. strtok(3) keeps its state in a hidden static, * so two interleaved tokenisations corrupt each other silently; it is not * wrapped. Running out of tokens is success with *dest NULL. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strtok_r(char *str, const char *delim, char **saveptr, char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strsep(char **stringp, const char *delim, char **dest); /* * The message for a status, into the caller's buffer. Knows this library's own * statuses as well as errno values, which strerror_r(3) could not. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strerror(int status, char *buf, size_t buflen); /* ---------------------------------------------------------------------- */ /* Streams -- src/stream.c */ /* ---------------------------------------------------------------------- */ /* * Where a function has a genuine "nothing more to read" outcome, that is * AKERR_EOF rather than AKERR_IO, so a read loop can tell the end of its input * from the failure of it. That applies to aksl_fgetc, aksl_fgets, aksl_getline, * aksl_getdelim and aksl_fscanf. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_fseek(FILE *stream, long offset, int whence); akerr_ErrorContext AKERR_NOIGNORE *aksl_ftell(FILE *stream, long *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_rewind(FILE *stream); akerr_ErrorContext AKERR_NOIGNORE *aksl_fseeko(FILE *stream, off_t offset, int whence); akerr_ErrorContext AKERR_NOIGNORE *aksl_ftello(FILE *stream, off_t *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_fgetpos(FILE *stream, fpos_t *pos); akerr_ErrorContext AKERR_NOIGNORE *aksl_fsetpos(FILE *stream, const fpos_t *pos); /* NULL stream means "every output stream", as in fflush(3); it is not an error. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_fflush(FILE *stream); akerr_ErrorContext AKERR_NOIGNORE *aksl_setvbuf(FILE *stream, char *buf, int mode, size_t size); akerr_ErrorContext AKERR_NOIGNORE *aksl_fgetc(FILE *stream, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_fputc(int c, FILE *stream); akerr_ErrorContext AKERR_NOIGNORE *aksl_ungetc(int c, FILE *stream); akerr_ErrorContext AKERR_NOIGNORE *aksl_fgets(char *s, size_t size, FILE *stream, size_t *len_out); akerr_ErrorContext AKERR_NOIGNORE *aksl_fputs(const char *s, FILE *stream); akerr_ErrorContext AKERR_NOIGNORE *aksl_getline(char **lineptr, size_t *n, FILE *stream, size_t *len_out); akerr_ErrorContext AKERR_NOIGNORE *aksl_getdelim(char **lineptr, size_t *n, int delim, FILE *stream, size_t *len_out); akerr_ErrorContext AKERR_NOIGNORE *aksl_feof(FILE *stream, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_ferror(FILE *stream, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_clearerr(FILE *stream); akerr_ErrorContext AKERR_NOIGNORE *aksl_fileno(FILE *stream, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_freopen(const char *pathname, const char *mode, FILE *stream, FILE **fp); akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopen(int fd, const char *mode, FILE **fp); akerr_ErrorContext AKERR_NOIGNORE *aksl_tmpfile(FILE **fp); /* * The scanf family takes the number of conversions the caller expects, because * comparing scanf(3)'s return against that number by hand at every call site is * the check everyone eventually forgets -- and forgetting it leaves the * unassigned arguments holding whatever they held before. Anything short of * `expected` is AKERR_VALUE. Pass 0 to opt out and read *assigned yourself. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_sscanf(const char *str, const char *format, int expected, int *assigned, ...) AKSL_SCANF_FORMAT(2, 5); akerr_ErrorContext AKERR_NOIGNORE *aksl_fscanf(FILE *stream, const char *format, int expected, int *assigned, ...) AKSL_SCANF_FORMAT(2, 5); akerr_ErrorContext AKERR_NOIGNORE *aksl_vsscanf(const char *str, const char *format, int expected, int *assigned, va_list args); akerr_ErrorContext AKERR_NOIGNORE *aksl_vfscanf(FILE *stream, const char *format, int expected, int *assigned, va_list args); akerr_ErrorContext AKERR_NOIGNORE *aksl_remove(const char *pathname); akerr_ErrorContext AKERR_NOIGNORE *aksl_rename(const char *oldpath, const char *newpath); /* * The template is rewritten in place, so it must be a writable buffer ending in * six literal X characters -- a string literal is a segfault, and that is * AKERR_VALUE here rather than a crash. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_mkstemp(char *template_, int *fd); akerr_ErrorContext AKERR_NOIGNORE *aksl_mkdtemp(char *template_); // Linked list functions akerr_ErrorContext AKERR_NOIGNORE *aksl_list_node_init(aksl_ListNode *node, void *data); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_append(aksl_ListNode *list, aksl_ListNode *obj); /* * `head` is required. Popping the head node has to move the caller's own head * pointer, and there is no way to do that from a node pointer alone -- the old * one-argument form left the caller aimed at a detached node. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_pop(aksl_ListNode **head, aksl_ListNode *node); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate(aksl_ListNode *list, aksl_ListNodeIterator iter, void *data); // Tree functions akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_node_init(aksl_TreeNode *node, void *leaf); akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_iterate(aksl_TreeNode *root, aksl_TreeNodeIterator iter, aksl_AllocFunc lalloc, aksl_FreeFunc lfree, uint8_t searchmode, void *data); /* ---------------------------------------------------------------------- */ /* Collections -- src/collections.c */ /* ---------------------------------------------------------------------- */ /* * Nothing in this section allocates unless its name says so. The list and tree * functions relink nodes the caller already owns, and the *_free_all forms take * the free function to use -- pass NULL for aksl_free -- so a caller drawing * from a fixed pool can hand over its own. Only aksl_strbuf_* owns memory. * * The bare-node list functions take the head by reference wherever the head * itself can move, which is the case a caller doing it by hand gets wrong. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_prepend(aksl_ListNode **head, aksl_ListNode *obj); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_insert_after(aksl_ListNode *node, aksl_ListNode *obj); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_insert_before(aksl_ListNode **head, aksl_ListNode *node, aksl_ListNode *obj); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_length(aksl_ListNode *head, size_t *dest); /* NULL and success when nothing matches, as everywhere else in this library. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_find(aksl_ListNode *head, aksl_ListNodePredicate pred, void *data, aksl_ListNode **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_reverse(aksl_ListNode **head); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_concat(aksl_ListNode *head, aksl_ListNode *other); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_free_all(aksl_ListNode **head, aksl_FreeFunc lfree); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate_reverse(aksl_ListNode *tail, aksl_ListNodeIterator iter, void *data); /* The tracked container. push/unshift are O(1); length is a field, not a walk. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_init(aksl_List *list); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_push(aksl_List *list, aksl_ListNode *obj); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_unshift(aksl_List *list, aksl_ListNode *obj); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_remove(aksl_List *list, aksl_ListNode *node); akerr_ErrorContext AKERR_NOIGNORE *aksl_list_clear(aksl_List *list, aksl_FreeFunc lfree); /* * An unbalanced binary search tree. Inserting already-sorted data gives a * degenerate chain, which the traversal then refuses past AKSL_TREE_MAX_DEPTH -- * bounded rather than dangerous, but this is not a balanced tree and does not * claim to be. These are the functions that set and read aksl_TreeNode.parent. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_insert(aksl_TreeNode **root, aksl_TreeNode *node, aksl_TreeCompareFunc cmp); akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_find(aksl_TreeNode *root, void *leaf, aksl_TreeCompareFunc cmp, aksl_TreeNode **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_remove(aksl_TreeNode **root, aksl_TreeNode *node); akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_height(aksl_TreeNode *root, int *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_count(aksl_TreeNode *root, size_t *dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_free_all(aksl_TreeNode **root, aksl_FreeFunc lfree); /* * FNV-1a alongside djb2. It XORs then multiplies rather than multiplying then * adding, which mixes the low bits better on short keys that share a prefix -- * which is what identifiers in a symbol table look like. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_fnv1a(const char *str, size_t len, uint32_t *hashval); akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_fnv1a_str(const char *str, uint32_t *hashval); /* * Fixed-capacity string-keyed hash map: open addressing, linear probing, the * caller's slot array, keys copied into the slots so the map owns them. * * It refuses rather than resizes when full, deliberately. A table that * reallocates is a table whose entry pointers move underneath anything holding * one; a table that cannot grow has a worst case you can state. The cost is that * the caller sizes it, which is why init takes the array rather than making one. */ #define AKSL_HASHMAP_MAX_KEY 64 #define AKSL_HASHMAP_SLOT_EMPTY 0 /** Never used; ends a probe chain */ #define AKSL_HASHMAP_SLOT_OCCUPIED 1 /** Holds a live key and value */ #define AKSL_HASHMAP_SLOT_DELETED 2 /** Tombstone; reusable, but does not end a chain */ typedef struct aksl_HashEntry { char key[AKSL_HASHMAP_MAX_KEY]; void *value; uint8_t state; } aksl_HashEntry; typedef struct aksl_HashMap { aksl_HashEntry *slots; size_t capacity; size_t count; } aksl_HashMap; akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_init(aksl_HashMap *map, aksl_HashEntry *slots, size_t capacity); akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_put(aksl_HashMap *map, const char *key, void *value); /* A missing key is *found = 0 and success; looking and not finding is not an error. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_get(aksl_HashMap *map, const char *key, void **value, int *found); akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_remove(aksl_HashMap *map, const char *key, int *removed); akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_iterate(aksl_HashMap *map, aksl_HashMapIterator iter, void *data); /* * Growable string buffer -- the one thing here that owns memory, which is what * makes the bounded formatting wrappers usable when the output length is not * known in advance. Capacity doubles, so n appends cost O(n) amortised, and the * contents are always NUL-terminated so aksl_strbuf_cstr is valid at any point. */ typedef struct aksl_StrBuf { char *data; size_t length; /** Bytes in use, terminator not counted */ size_t capacity; /** Bytes allocated, terminator included */ } aksl_StrBuf; akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_init(aksl_StrBuf *buf, size_t initial); akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append(aksl_StrBuf *buf, const char *s); akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append_bytes(aksl_StrBuf *buf, const char *s, size_t n); akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append_char(aksl_StrBuf *buf, char c); akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_appendf(aksl_StrBuf *buf, const char *format, ...) AKSL_PRINTF_FORMAT(2, 3); akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_vappendf(aksl_StrBuf *buf, const char *format, va_list args); akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_reset(aksl_StrBuf *buf); /* Points into the buffer; the next append invalidates it. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_cstr(aksl_StrBuf *buf, const char **dest); akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_free(aksl_StrBuf *buf); #ifdef __cplusplus } #endif #endif