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>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
486
src/string.c
Normal file
486
src/string.c
Normal file
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
* string.h wrappers -- TODO.md section 3.1.
|
||||
*
|
||||
* This is the section akbasic needed most and could not have: across its source
|
||||
* it calls strlen 37 times, strcmp 16, strncpy 15 and strstr once, every one of
|
||||
* them raw because there was nothing here to call instead. Ten of those sites
|
||||
* are the same idiom written out by hand -- a length check, then strncpy, then
|
||||
* an explicit NUL -- which is exactly the "truncation reported as an error
|
||||
* rather than silently accepted" that TODO.md 3.1 asks for.
|
||||
*
|
||||
* Two conventions run through the whole file.
|
||||
*
|
||||
* The copying functions take the size of the destination, and the ones named
|
||||
* after libc functions that do not take one take it anyway. strcpy(3) and
|
||||
* strcat(3) cannot be called safely without knowing how much room the
|
||||
* destination has, and a wrapper that accepts the same arguments as strcpy
|
||||
* would be an error-handling wrapper around a buffer overflow. Truncation is
|
||||
* AKERR_OUTOFBOUNDS: it is the failure these functions exist to report, and
|
||||
* nothing is written to the destination when it happens, so a caller who
|
||||
* ignores the status does not get a half-copied string either. aksl_strncpy
|
||||
* also always terminates, which strncpy(3) famously does not.
|
||||
*
|
||||
* The searching functions answer through an out-param and treat "not found" as
|
||||
* a successful answer of NULL rather than as an error -- absent is an ordinary
|
||||
* answer to "where is this", and a library that raised on it would have every
|
||||
* caller handling a non-error as an error.
|
||||
*/
|
||||
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "aksl_internal.h"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Length */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strlen(const char *s, size_t *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
*dest = strlen(s);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* strnlen(3) stops at maxlen whether or not it found a terminator, so a result
|
||||
* equal to maxlen means "at least this long" rather than "this long". That
|
||||
* ambiguity is the reason to use it -- it is how you measure a buffer that may
|
||||
* not be terminated -- so it is reported rather than hidden: *dest is the
|
||||
* length and the call succeeds either way.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strnlen(const char *s, size_t maxlen, size_t *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
*dest = strnlen(s, maxlen);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Copying and concatenation */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Bounded copy. dstsize is the whole destination buffer, terminator included,
|
||||
* so `char buf[64]` pairs with `sizeof(buf)`. A source that does not fit is
|
||||
* AKERR_OUTOFBOUNDS and the destination is left as an empty string rather than
|
||||
* as a truncated one -- a caller who ignores the error gets nothing, which is
|
||||
* far easier to notice than a plausible-looking prefix.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcpy(char *dst, size_t dstsize, const char *src)
|
||||
{
|
||||
size_t len = 0;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||||
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||||
FAIL_ZERO_RETURN(e, dstsize, AKERR_VALUE, "dstsize=0 leaves no room for a terminator");
|
||||
dst[0] = '\0';
|
||||
len = strlen(src);
|
||||
FAIL_NONZERO_RETURN(e, (len >= dstsize), AKERR_OUTOFBOUNDS,
|
||||
"source is %zu bytes and the destination holds %zu including the terminator",
|
||||
len, dstsize);
|
||||
memcpy(dst, src, len + 1);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* At most n bytes of src, and always terminated.
|
||||
*
|
||||
* strncpy(3) does two surprising things that this does not: it leaves the
|
||||
* destination unterminated when the source is at least n bytes long, and it
|
||||
* pads the remainder with NULs when the source is shorter, which turns a short
|
||||
* copy into a full-length write. Here n bounds how much of the source is
|
||||
* considered, dstsize bounds the write, and the result is always a C string.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncpy(char *dst, size_t dstsize, const char *src, size_t n)
|
||||
{
|
||||
size_t len = 0;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||||
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||||
FAIL_ZERO_RETURN(e, dstsize, AKERR_VALUE, "dstsize=0 leaves no room for a terminator");
|
||||
dst[0] = '\0';
|
||||
len = strnlen(src, n);
|
||||
FAIL_NONZERO_RETURN(e, (len >= dstsize), AKERR_OUTOFBOUNDS,
|
||||
"%zu bytes to copy and the destination holds %zu including the terminator",
|
||||
len, dstsize);
|
||||
memcpy(dst, src, len);
|
||||
dst[len] = '\0';
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Bounded append. dstsize is again the whole buffer, so the room actually
|
||||
* available is dstsize minus what is already in there. The destination must
|
||||
* already be a terminated string within dstsize; if it is not, that is
|
||||
* AKERR_VALUE rather than a walk off the end looking for a NUL that is not
|
||||
* there.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcat(char *dst, size_t dstsize, const char *src)
|
||||
{
|
||||
size_t used = 0;
|
||||
size_t len = 0;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||||
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||||
FAIL_ZERO_RETURN(e, dstsize, AKERR_VALUE, "dstsize=0 leaves no room for a terminator");
|
||||
used = strnlen(dst, dstsize);
|
||||
FAIL_NONZERO_RETURN(e, (used == dstsize), AKERR_VALUE,
|
||||
"destination is not terminated within its %zu bytes", dstsize);
|
||||
len = strlen(src);
|
||||
FAIL_NONZERO_RETURN(e, (used + len >= dstsize), AKERR_OUTOFBOUNDS,
|
||||
"%zu bytes in use plus %zu to append exceeds the %zu-byte destination",
|
||||
used, len, dstsize);
|
||||
memcpy(dst + used, src, len + 1);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncat(char *dst, size_t dstsize, const char *src, size_t n)
|
||||
{
|
||||
size_t used = 0;
|
||||
size_t len = 0;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||||
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||||
FAIL_ZERO_RETURN(e, dstsize, AKERR_VALUE, "dstsize=0 leaves no room for a terminator");
|
||||
used = strnlen(dst, dstsize);
|
||||
FAIL_NONZERO_RETURN(e, (used == dstsize), AKERR_VALUE,
|
||||
"destination is not terminated within its %zu bytes", dstsize);
|
||||
len = strnlen(src, n);
|
||||
FAIL_NONZERO_RETURN(e, (used + len >= dstsize), AKERR_OUTOFBOUNDS,
|
||||
"%zu bytes in use plus %zu to append exceeds the %zu-byte destination",
|
||||
used, len, dstsize);
|
||||
memcpy(dst + used, src, len);
|
||||
dst[used + len] = '\0';
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Duplication */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* *dest is the caller's, to release with aksl_free or aksl_freep. */
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strdup(const char *s, char **dest)
|
||||
{
|
||||
char *copy = NULL;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
*dest = NULL;
|
||||
errno = 0;
|
||||
copy = strdup(s);
|
||||
FAIL_ZERO_RETURN(e, copy, AKSL_ERRNO_OR(ENOMEM), "%zu bytes", strlen(s) + 1);
|
||||
*dest = copy;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strndup(const char *s, size_t n, char **dest)
|
||||
{
|
||||
char *copy = NULL;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
*dest = NULL;
|
||||
errno = 0;
|
||||
copy = strndup(s, n);
|
||||
FAIL_ZERO_RETURN(e, copy, AKSL_ERRNO_OR(ENOMEM), "%zu bytes", n + 1);
|
||||
*dest = copy;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Comparison */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* The comparison result goes through *dest because the return value is spoken
|
||||
* for by the error context. The sign is the usual one: negative, zero or
|
||||
* positive as a sorts before, equal to, or after b.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcmp(const char *a, const char *b, int *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
*dest = strcmp(a, b);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncmp(const char *a, const char *b, size_t n, int *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
*dest = strncmp(a, b, n);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Case-insensitive comparison. akbasic folds case by hand at three sites --
|
||||
* twice with an open-coded loop and once through <ctype.h> -- because BASIC
|
||||
* verb and function names are case-insensitive while variable names are not.
|
||||
* All three of those are this call.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcasecmp(const char *a, const char *b, int *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
*dest = strcasecmp(a, b);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncasecmp(const char *a, const char *b, size_t n, int *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
*dest = strncasecmp(a, b, n);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Locale-aware collation. strcoll(3) has no error return of its own, so a
|
||||
* malformed multibyte sequence in the current locale shows up only as errno
|
||||
* being set -- which is why errno is cleared first and consulted after.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcoll(const char *a, const char *b, int *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||||
errno = 0;
|
||||
*dest = strcoll(a, b);
|
||||
FAIL_NONZERO_RETURN(e, errno, errno, "strcoll failed in the current locale");
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Searching */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Every function in this section writes NULL to *dest and succeeds when there
|
||||
* is nothing to find. "Absent" is an answer, not a failure; raising on it would
|
||||
* make every caller handle a non-error as an error, and the pool slot that
|
||||
* error consumed would be pure waste.
|
||||
*
|
||||
* *dest points into the caller's own string, so it lives exactly as long as the
|
||||
* string does and must not be freed.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strchr(const char *s, int c, char **dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
*dest = strchr(s, c);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strrchr(const char *s, int c, char **dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||||
*dest = strrchr(s, c);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strstr(const char *haystack, const char *needle, char **dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, haystack, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||||
(void *)haystack, (void *)needle, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, needle, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||||
(void *)haystack, (void *)needle, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||||
(void *)haystack, (void *)needle, (void *)dest);
|
||||
*dest = strstr(haystack, needle);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Case-insensitive search. strcasestr(3) is a GNU extension that also exists on
|
||||
* the BSDs and musl, but it is not in any standard, so it is open-coded here
|
||||
* rather than depending on _GNU_SOURCE reaching every consumer's build. The
|
||||
* naive scan is the same complexity as glibc's fallback and this is not the hot
|
||||
* path in anything.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcasestr(const char *haystack, const char *needle, char **dest)
|
||||
{
|
||||
size_t hlen = 0;
|
||||
size_t nlen = 0;
|
||||
size_t i = 0;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, haystack, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||||
(void *)haystack, (void *)needle, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, needle, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||||
(void *)haystack, (void *)needle, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||||
(void *)haystack, (void *)needle, (void *)dest);
|
||||
*dest = NULL;
|
||||
nlen = strlen(needle);
|
||||
/* An empty needle matches at the start, as strstr(3) has it. */
|
||||
if ( nlen == 0 ) {
|
||||
*dest = (char *)haystack;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
hlen = strlen(haystack);
|
||||
if ( nlen <= hlen ) {
|
||||
for ( i = 0; i <= hlen - nlen; i++ ) {
|
||||
if ( strncasecmp(haystack + i, needle, nlen) == 0 ) {
|
||||
*dest = (char *)(haystack + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strpbrk(const char *s, const char *accept, char **dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||||
(void *)s, (void *)accept, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, accept, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||||
(void *)s, (void *)accept, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||||
(void *)s, (void *)accept, (void *)dest);
|
||||
*dest = strpbrk(s, accept);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strspn(const char *s, const char *accept, size_t *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||||
(void *)s, (void *)accept, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, accept, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||||
(void *)s, (void *)accept, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||||
(void *)s, (void *)accept, (void *)dest);
|
||||
*dest = strspn(s, accept);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcspn(const char *s, const char *reject, size_t *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, reject=%p, dest=%p",
|
||||
(void *)s, (void *)reject, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, reject, AKERR_NULLPOINTER, "s=%p, reject=%p, dest=%p",
|
||||
(void *)s, (void *)reject, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, reject=%p, dest=%p",
|
||||
(void *)s, (void *)reject, (void *)dest);
|
||||
*dest = strcspn(s, reject);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Tokenising */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* The reentrant tokeniser only. strtok(3) keeps its state in a hidden static,
|
||||
* which makes two interleaved tokenisations silently corrupt each other and
|
||||
* makes any use from a thread a bug, so it is not wrapped here at all -- see
|
||||
* the note on threads in README.md.
|
||||
*
|
||||
* Running out of tokens is success with *dest NULL, for the same reason a failed
|
||||
* search is: it is how the loop ends, not a fault.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtok_r(char *str, const char *delim, char **saveptr, char **dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, delim, AKERR_NULLPOINTER, "delim=%p, saveptr=%p, dest=%p",
|
||||
(void *)delim, (void *)saveptr, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, saveptr, AKERR_NULLPOINTER, "delim=%p, saveptr=%p, dest=%p",
|
||||
(void *)delim, (void *)saveptr, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "delim=%p, saveptr=%p, dest=%p",
|
||||
(void *)delim, (void *)saveptr, (void *)dest);
|
||||
*dest = strtok_r(str, delim, saveptr);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* strsep(3) differs from strtok_r in returning empty tokens between adjacent
|
||||
* delimiters, which is what you want for parsing "a::b" as three fields rather
|
||||
* than two. It advances *stringp itself and sets it to NULL when done.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strsep(char **stringp, const char *delim, char **dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, stringp, AKERR_NULLPOINTER, "stringp=%p, delim=%p, dest=%p",
|
||||
(void *)stringp, (void *)delim, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, delim, AKERR_NULLPOINTER, "stringp=%p, delim=%p, dest=%p",
|
||||
(void *)stringp, (void *)delim, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "stringp=%p, delim=%p, dest=%p",
|
||||
(void *)stringp, (void *)delim, (void *)dest);
|
||||
*dest = strsep(stringp, delim);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* errno messages */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* The message for a status, into the caller's own buffer.
|
||||
*
|
||||
* Deliberately not built on strerror_r(3). There are two incompatible functions
|
||||
* by that name -- the XSI one returns int, the GNU one returns char * and may
|
||||
* not touch the buffer at all -- and which one a translation unit gets depends
|
||||
* on feature-test macros that a consumer of this library cannot influence from
|
||||
* in here. strerror(3) itself is not thread-safe, so that is no way out either.
|
||||
*
|
||||
* libakerror already carries the full errno name table (its generrno.sh builds
|
||||
* one at configure time), and its registry also knows the names of this
|
||||
* library's own non-errno statuses -- AKERR_NULLPOINTER, AKERR_ITERATOR_BREAK
|
||||
* and the rest -- which strerror_r could never name. So the lookup goes there,
|
||||
* and a status neither table recognises comes back as its own number rather
|
||||
* than as the bare string "Unknown Error", which tells the reader nothing.
|
||||
*
|
||||
* The message is truncated to nothing rather than to a prefix if it does not
|
||||
* fit, on the same principle as aksl_strcpy above.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strerror(int status, char *buf, size_t buflen)
|
||||
{
|
||||
const char *name = NULL;
|
||||
int needed = 0;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf=%p", (void *)buf);
|
||||
FAIL_ZERO_RETURN(e, buflen, AKERR_VALUE, "buflen=0 leaves no room for a terminator");
|
||||
buf[0] = '\0';
|
||||
name = akerr_name_for_status(status, NULL);
|
||||
/*
|
||||
* akerr_name_for_status never returns NULL; it returns this exact string
|
||||
* for anything it does not have a name for.
|
||||
*/
|
||||
if ( name == NULL || strcmp(name, "Unknown Error") == 0 ) {
|
||||
needed = snprintf(buf, buflen, "Unknown status %d", status);
|
||||
} else {
|
||||
needed = snprintf(buf, buflen, "%s", name);
|
||||
}
|
||||
FAIL_NONZERO_RETURN(e, (needed < 0), AKERR_IO, "could not format status %d", status);
|
||||
if ( (size_t)needed >= buflen ) {
|
||||
buf[0] = '\0';
|
||||
FAIL_RETURN(e, AKERR_OUTOFBOUNDS,
|
||||
"the message for status %d needs %d bytes and the buffer holds %zu",
|
||||
status, needed + 1, buflen);
|
||||
}
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
Reference in New Issue
Block a user