wraith-lang/lib/dropt/dropt_string.c

693 lines
17 KiB
C

/** dropt_string.c
*
* String routines for dropt.
*
* Copyright (C) 2006-2018 James D. Lin <jamesdlin@berkeley.edu>
*
* The latest version of this file can be downloaded from:
* <http://www.taenarum.com/software/dropt/>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifdef _MSC_VER
#include <tchar.h>
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <wctype.h>
#include <stdio.h>
#include <assert.h>
#if __STDC_VERSION__ >= 199901L
#include <stdint.h>
#else
/* Compatibility junk for things that don't yet support ISO C99. */
#if defined _MSC_VER || defined __BORLANDC__
#ifndef va_copy
#define va_copy(dest, src) (dest = (src))
#endif
#else
#ifndef va_copy
#error Unsupported platform. va_copy is not defined.
#endif
#endif
#ifndef SIZE_MAX
#define SIZE_MAX ((size_t) -1)
#endif
#endif
#include "dropt_string.h"
#ifndef MAX
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#endif
#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif
#ifdef DROPT_DEBUG_STRING_BUFFERS
enum { default_stringstream_buffer_size = 1 };
#define GROWN_STRINGSTREAM_BUFFER_SIZE(oldSize, minAmount) \
((oldSize) + (minAmount))
#else
enum { default_stringstream_buffer_size = 256 };
#define GROWN_STRINGSTREAM_BUFFER_SIZE(oldSize, minAmount) \
MAX((oldSize) * 2, (oldSize) + (minAmount))
#endif
#ifndef DROPT_NO_STRING_BUFFERS
struct dropt_stringstream
{
/* The string buffer. */
dropt_char* string;
/* Size of the string buffer, in `dropt_char`s, including space for `NUL`.
*/
size_t maxSize;
/* Number of elements used in the string buffer, excluding `NUL`. */
size_t used;
};
#endif
/** dropt_safe_malloc
*
* A version of `malloc` that checks for integer overflow.
*
* PARAMETERS:
* IN numElements : The number of elements to allocate.
* IN elementSize : The size of each element, in bytes.
*
* RETURNS:
* A pointer to the allocated memory.
* Returns `NULL` if `numElements` is 0.
* Returns `NULL` on error.
*/
void*
dropt_safe_malloc(size_t numElements, size_t elementSize)
{
return dropt_safe_realloc(NULL, numElements, elementSize);
}
/** dropt_safe_realloc
*
* Wrapper around `realloc` to check for integer overflow.
*
* PARAMETERS:
* IN/OUT p : A pointer to the memory block to resize.
* If `NULL`, a new memory block of the specified size
* will be allocated.
* IN numElements : The number of elements to allocate.
* If 0, frees `p`.
* IN elementSize : The size of each element, in bytes.
*
* RETURNS:
* A pointer to the allocated memory.
* Returns `NULL` if `numElements` is 0.
* Returns `NULL` on error.
*/
void*
dropt_safe_realloc(void* p, size_t numElements, size_t elementSize)
{
size_t numBytes;
/* `elementSize` shouldn't legally be 0, but we check for it in case a
* caller got the argument order wrong.
*/
if (numElements == 0 || elementSize == 0)
{
/* The behavior of `realloc(p, 0)` is implementation-defined. Let's
* enforce a particular behavior.
*/
free(p);
assert(elementSize != 0);
return NULL;
}
numBytes = numElements * elementSize;
if (numBytes / elementSize != numElements)
{
/* Overflow. */
return NULL;
}
return realloc(p, numBytes);
}
/** dropt_strdup
*
* Duplicates a string.
*
* PARAMETERS:
* IN s : A `NUL`-terminated string to duplicate.
*
* RETURNS:
* The duplicated string. The caller is responsible for calling `free()`
* on it when no longer needed.
* Returns `NULL` on error.
*/
dropt_char*
dropt_strdup(const dropt_char* s)
{
return dropt_strndup(s, SIZE_MAX);
}
/** dropt_strndup
*
* Duplicates the first `n` characters of a string.
*
* PARAMETERS:
* IN s : The string to duplicate.
* IN n : The maximum number of `dropt_char`s to copy, excluding the
* `NUL`-terminator.
*
* RETURNS:
* The duplicated string, which is always `NUL`-terminated. The caller is
* responsible for calling `free()` on it when no longer needed.
* Returns `NULL` on error.
*/
dropt_char*
dropt_strndup(const dropt_char* s, size_t n)
{
dropt_char* copy;
size_t len = 0;
assert(s != NULL);
while (len < n && s[len] != DROPT_TEXT_LITERAL('\0'))
{
len++;
}
if (len + 1 < len)
{
/* This overflow check shouldn't be strictly necessary. `len` can be at
* most `SIZE_MAX`, so `SIZE_MAX + 1` can wrap around to 0, but
* `dropt_safe_malloc` will return `NULL` for a 0-sized allocation.
* However, favor defensive paranoia.
*/
return NULL;
}
copy = dropt_safe_malloc(len + 1 /* NUL */, sizeof *copy);
if (copy != NULL)
{
memcpy(copy, s, len * sizeof *copy);
copy[len] = DROPT_TEXT_LITERAL('\0');
}
return copy;
}
/** dropt_stricmp
*
* Compares two `NUL`-terminated strings ignoring case differences. Not
* recommended for non-ASCII strings.
*
* PARAMETERS:
* IN s, t : The strings to compare.
*
* RETURNS:
* 0 if the strings are equivalent,
* < 0 if `s` should precede `t`,
* > 0 if `s` should follow `t`.
*/
int
dropt_stricmp(const dropt_char* s, const dropt_char* t)
{
assert(s != NULL);
assert(t != NULL);
return dropt_strnicmp(s, t, SIZE_MAX);
}
/** dropt_strnicmp
*
* Compares the first `n` characters of two strings, ignoring case
* differences. Not recommended for non-ASCII strings.
*
* PARAMETERS:
* IN s, t : The strings to compare.
* IN n : The maximum number of `dropt_char`s to compare.
*
* RETURNS:
* 0 if the strings are equivalent,
* < 0 if `s` should precede `t`,
* > 0 if `s` should follow `t`.
*/
int
dropt_strnicmp(const dropt_char* s, const dropt_char* t, size_t n)
{
assert(s != NULL);
assert(t != NULL);
if (s == t) { return 0; }
while (n--)
{
if (*s == DROPT_TEXT_LITERAL('\0') && *t == DROPT_TEXT_LITERAL('\0'))
{
break;
}
else if (*s == *t || dropt_tolower(*s) == dropt_tolower(*t))
{
s++;
t++;
}
else
{
return (dropt_tolower(*s) < dropt_tolower(*t))
? -1
: +1;
}
}
return 0;
}
#ifndef DROPT_NO_STRING_BUFFERS
/** dropt_vsnprintf
*
* `vsnprintf` wrapper to provide ISO C99-compliant behavior.
*
* PARAMETERS:
* OUT s : The destination buffer. May be `NULL` if `n` is 0.
* If non-`NULL`, always `NUL`-terminated.
* IN n : The size of the destination buffer, measured in
* `dropt_char`s.
* IN format : `printf`-style format specifier. Must not be `NULL`.
* IN args : Arguments to insert into the formatted string.
*
* RETURNS:
* The number of characters that would be written to the destination
* buffer if it's sufficiently large, excluding the `NUL`-terminator.
* Returns -1 on error.
*/
int
dropt_vsnprintf(dropt_char* s, size_t n, const dropt_char* format, va_list args)
{
#if __STDC_VERSION__ >= 199901L || __GNUC__
/* ISO C99-compliant.
*
* As far as I can tell, gcc's implementation of `vsnprintf` has always
* matched the behavior required by the C99 standard (which is to return
* the necessary buffer size).
*
* Note that this won't work with `wchar_t` because there is no true,
* standard `wchar_t` equivalent of `snprintf`. `swprintf` comes close but
* doesn't return the necessary buffer size (and the standard does not
* provide a guaranteed way to test if truncation occurred), and its
* format string can't be used interchangeably with `snprintf`.
*
* It's simpler not to support `wchar_t` on non-Windows platforms.
*/
assert(format != NULL);
return vsnprintf(s, n, format, args);
#elif defined __BORLANDC__
/* Borland's compiler neglects to `NUL`-terminate. */
int ret;
assert(format != NULL);
ret = vsnprintf(s, n, format, args);
if (n != 0) { s[n - 1] = DROPT_TEXT_LITERAL('\0'); }
return ret;
#elif defined _MSC_VER
/* `_vsntprintf` and `_vsnprintf_s` on Windows don't have C99 semantics;
* they return -1 if truncation occurs.
*/
va_list argsCopy;
int ret;
assert(format != NULL);
va_copy(argsCopy, args);
ret = _vsctprintf(format, argsCopy);
va_end(argsCopy);
if (n != 0)
{
assert(s != NULL);
#if _MSC_VER >= 1400
(void) _vsntprintf_s(s, n, _TRUNCATE, format, args);
#else
/* This version doesn't necessarily `NUL`-terminate. Sigh. */
(void) _vsnprintf(s, n, format, args);
s[n - 1] = DROPT_TEXT_LITERAL('\0');
#endif
}
return ret;
#else
#error Unsupported platform. dropt_vsnprintf unimplemented.
return -1;
#endif
}
/** See `dropt_vsnprintf`. */
int
dropt_snprintf(dropt_char* s, size_t n, const dropt_char* format, ...)
{
int ret;
va_list args;
va_start(args, format);
ret = dropt_vsnprintf(s, n, format, args);
va_end(args);
return ret;
}
/** dropt_vasprintf
*
* Allocates a formatted string with `vprintf` semantics.
*
* PARAMETERS:
* IN format : `printf`-style format specifier. Must not be `NULL`.
* IN args : Arguments to insert into the formatted string.
*
* RETURNS:
* The formatted string, which is always NUL-terminated. The caller is
* responsible for calling `free()` on it when no longer needed.
* Returns `NULL` on error.
*/
dropt_char*
dropt_vasprintf(const dropt_char* format, va_list args)
{
dropt_char* s = NULL;
int len;
va_list argsCopy;
assert(format != NULL);
va_copy(argsCopy, args);
len = dropt_vsnprintf(NULL, 0, format, argsCopy);
va_end(argsCopy);
if (len >= 0)
{
size_t n = len + 1 /* NUL */;
s = dropt_safe_malloc(n, sizeof *s);
if (s != NULL)
{
dropt_vsnprintf(s, n, format, args);
}
}
return s;
}
/** See `dropt_vasprintf`. */
dropt_char*
dropt_asprintf(const dropt_char* format, ...)
{
dropt_char* s;
va_list args;
va_start(args, format);
s = dropt_vasprintf(format, args);
va_end(args);
return s;
}
/** dropt_ssopen
*
* Constructs a new `dropt_stringstream`.
*
* RETURNS:
* An initialized `dropt_stringstream`. The caller is responsible for
* calling either `dropt_ssclose()` or `dropt_ssfinalize()` on it when
* no longer needed.
* Returns `NULL` on error.
*/
dropt_stringstream*
dropt_ssopen(void)
{
dropt_stringstream* ss = malloc(sizeof *ss);
if (ss != NULL)
{
ss->used = 0;
ss->maxSize = default_stringstream_buffer_size;
ss->string = dropt_safe_malloc(ss->maxSize, sizeof *ss->string);
if (ss->string == NULL)
{
free(ss);
ss = NULL;
}
else
{
ss->string[0] = DROPT_TEXT_LITERAL('\0');
}
}
return ss;
}
/** dropt_ssclose
*
* Destroys a `dropt_stringstream`.
*
* PARAMETERS:
* IN/OUT ss : The `dropt_stringstream`.
*/
void
dropt_ssclose(dropt_stringstream* ss)
{
if (ss != NULL)
{
free(ss->string);
free(ss);
}
}
/** dropt_ssgetfreespace
*
* RETURNS:
* The amount of free space in the `dropt_stringstream`'s internal buffer,
* measured in `dropt_char`s. Space used for the `NUL`-terminator is
* considered free. (The amount of free space therefore is always
* positive.)
*/
static size_t
dropt_ssgetfreespace(const dropt_stringstream* ss)
{
assert(ss != NULL);
assert(ss->maxSize > 0);
assert(ss->maxSize > ss->used);
return ss->maxSize - ss->used;
}
/** dropt_ssresize
*
* Resizes a `dropt_stringstream`'s internal buffer. If the requested
* size is less than the amount of buffer already in use, the buffer will
* be shrunk to the minimum size necessary.
*
* PARAMETERS:
* IN/OUT ss : The `dropt_stringstream`.
* IN n : The desired buffer size, in `dropt_char`s.
*
* RETURNS:
* The new size of the `dropt_stringstream`'s buffer in `dropt_char`s,
* including space for a terminating `NUL`.
*/
static size_t
dropt_ssresize(dropt_stringstream* ss, size_t n)
{
assert(ss != NULL);
/* Don't allow shrinking if it will truncate the string. */
if (n < ss->maxSize) { n = MAX(n, ss->used + 1 /* NUL */); }
/* There should always be a buffer to point to. */
assert(n > 0);
if (n != ss->maxSize)
{
dropt_char* p = dropt_safe_realloc(ss->string, n, sizeof *ss->string);
if (p != NULL)
{
ss->string = p;
ss->maxSize = n;
assert(ss->maxSize > 0);
}
}
return ss->maxSize;
}
/** dropt_ssclear
*
* Clears and re-initializes a `dropt_stringstream`.
*
* PARAMETERS:
* IN/OUT ss : The `dropt_stringstream`.
*/
void
dropt_ssclear(dropt_stringstream* ss)
{
assert(ss != NULL);
ss->string[0] = DROPT_TEXT_LITERAL('\0');
ss->used = 0;
dropt_ssresize(ss, default_stringstream_buffer_size);
}
/** dropt_ssfinalize
*
* Finalizes a `dropt_stringstream`; returns the contained string and
* destroys the `dropt_stringstream`.
*
* PARAMETERS:
* IN/OUT ss : The `dropt_stringstream`.
*
* RETURNS:
* The `dropt_stringstream`'s string, which is always `NUL`-terminated.
* Note that the caller assumes ownership of the returned string and is
* responsible for calling `free()` on it when no longer needed.
*/
dropt_char*
dropt_ssfinalize(dropt_stringstream* ss)
{
dropt_char* s;
assert(ss != NULL);
/* Shrink to fit. */
dropt_ssresize(ss, 0);
s = ss->string;
ss->string = NULL;
dropt_ssclose(ss);
return s;
}
/** dropt_ssgetstring
*
* PARAMETERS:
* IN ss : The `dropt_stringstream`.
*
* RETURNS:
* The `dropt_stringstream`'s string, which is always `NUL`-terminated.
* The returned string will no longer be valid if further operations are
* performed on the `dropt_stringstream` or if the `dropt_stringstream`
* is closed.
*/
const dropt_char*
dropt_ssgetstring(const dropt_stringstream* ss)
{
assert(ss != NULL);
return ss->string;
}
/** dropt_vssprintf
*
* Appends a formatted string with `vprintf` semantics to a
* `dropt_stringstream`.
*
* PARAMETERS:
* IN/OUT ss : The `dropt_stringstream`.
* IN format : `printf`-style format specifier. Must not be `NULL`.
* IN args : Arguments to insert into the formatted string.
*
* RETURNS:
* The number of characters written to the `dropt_stringstream`, excluding
* the `NUL`-terminator.
* Returns a negative value on error.
*/
int
dropt_vssprintf(dropt_stringstream* ss, const dropt_char* format, va_list args)
{
int n;
va_list argsCopy;
assert(ss != NULL);
assert(format != NULL);
va_copy(argsCopy, args);
n = dropt_vsnprintf(NULL, 0, format, argsCopy);
va_end(argsCopy);
if (n > 0)
{
size_t available = dropt_ssgetfreespace(ss);
if ((unsigned int) n >= available)
{
/* It's possible that `newSize < ss->maxSize` if
* `GROWN_STRINGSTREAM_BUFFER_SIZE()` overflows, but it should be
* safe since we'll recompute the available space.
*/
size_t newSize = GROWN_STRINGSTREAM_BUFFER_SIZE(ss->maxSize, n);
dropt_ssresize(ss, newSize);
available = dropt_ssgetfreespace(ss);
}
assert(available > 0); /* Space always is reserved for NUL. */
/* `snprintf`'s family of functions return the number of characters
* that would be output with a sufficiently large buffer, excluding
* `NUL`.
*/
n = dropt_vsnprintf(ss->string + ss->used, available, format, args);
/* We couldn't allocate enough space. */
if ((unsigned int) n >= available) { n = -1; }
if (n > 0) { ss->used += n; }
}
return n;
}
/** See `dropt_vssprintf`. */
int
dropt_ssprintf(dropt_stringstream* ss, const dropt_char* format, ...)
{
int n;
va_list args;
va_start(args, format);
n = dropt_vssprintf(ss, format, args);
va_end(args);
return n;
}
#endif /* DROPT_NO_STRING_BUFFERS */