Wrap the strings, streams and collections the wishlist asked for
TODO.md section 3.1's high-priority list and section 3.6's data
structures. This is the surface akbasic went without: it makes 10 calls
into this library and 116 to raw libc, and 69 of those 116 are strlen,
strcmp, strncpy and strstr.
Three new translation units, because src/stdlib.c covering four times
what it did would stop being readable:
src/string.c lengths, bounded copy and concatenation, duplication,
comparison including the case-insensitive forms,
searching, the reentrant tokenisers, and a status
message that knows this library's own statuses as
well as errno's.
src/stream.c positioning, flushing and buffering, character and
line I/O, stream state, freopen/fdopen/tmpfile,
formatted input, and the file operations.
src/collections.c the list functions that were missing, a head/tail
container so append is O(1), a binary search tree,
FNV-1a, a fixed-capacity hash map and a growable
string buffer.
Two conventions run through all of it. The copying functions take the
destination size even where the libc function they are named for does
not, because strcpy(3) cannot be called safely without it, and
truncation is an error that writes nothing rather than a plausible
prefix -- this is the idiom akbasic writes out by hand at ten sites.
The searching functions treat "not found" as a successful answer of
NULL, because absent is an answer and raising on it would make every
caller handle a non-error.
The hash map is akbasic's src/symtab.c generalised: open-addressed with
linear probing over a caller-supplied slot array, keys copied into fixed
slots so the map owns them, tombstones on delete so a removal cannot cut
a probe chain, and a refusal rather than a resize when full.
Tests: 16 binaries, green under the normal and sanitizer builds. The
scanf wrappers take the number of conversions the caller expects, since
comparing scanf(3)'s return against that by hand is the check everyone
eventually forgets.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,8 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
/* off_t, for the aksl_fseeko/aksl_ftello pair. POSIX, like aksl_realpath. */
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -57,8 +59,11 @@ extern "C" {
|
||||
#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 {
|
||||
@@ -96,10 +101,26 @@ typedef struct aksl_TreeNode {
|
||||
*/
|
||||
#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
|
||||
@@ -232,6 +253,128 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_realpath_alloc(const char *restrict path
|
||||
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);
|
||||
@@ -247,6 +390,114 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate(aksl_ListNode *list, aksl_L
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user