About
Vstring is a simple string building API for the C programming language. The
API does not make any thread-safety guarantees; sharing vstrings between
threads requires some form of synchronization. (At some point, I may make a
concurrent string API, but that's neither here nor there.) Vstring supports
static and dynamic buffers (static buffers are upgraded to dynamic buffers
as needed), and can efficiently parse signed and unsigned integers.
The API is intended to promote safety in string manipulation as well as to
provide a means to avoid hugely expensive printf-family calls for common
operations.
API
A vstring type is defined by the API; this type contains all necessary
information / metadata for modifying the underlying buffer. The type is not
opaque (because opaque types suck), but it is not recommended to play with
the type outside of the API. (If you find the need to do this, fix / extend
the API and send a pull request!)
Compiling
Simply #include <vstring.h>. If you use vs_pushdouble, you may need to link
in the system's math library if that is not part of libc.
vstring
The vstring type is defined as follows:
typedef struct vstring {
char *contents;
uint32_t type;
uint32_t flags;
uint64_t pointer;
uint64_t size;
} vstring;
- The contents pointer represents the underlying buffer.
- The type member is a bitmap containing information about the type
of the vstring instance.
- The flags member is a bitmap containing metadata about the vstring
instance. These flags are intended for API internal use and should
not be relied upon by consumers of the API.
- The pointer member is used as an offset into the contents buffer.
It points to the end of the string + 1 byte.
- The size member contains the total capacity of the contents buffer.
Types
Three sorts of vstrings exist:
* Static strings (VS_TYPE_STATIC): these strings are backed by a static
buffer and cannot grow.
* Growable static strings (VS_TYPE_GROWABLE): these strings are backed
by a static buffer, but may be upgraded to dynamic strings if an append
operation would cause an overflow.
* Dynamic strings (VS_TYPE_DYNAMIC): these strings are backed by a
dynamically allocated buffer and may grow if an append option would
cause an overflow.
Allocation
typedef struct vstring_malloc {
void *(*vs_malloc)(size_t);
void *(*vs_realloc)(void *, size_t);
void (*vs_free)(void *);
} vstring_malloc;
The vstring_malloc type provides a means for using a custom memory
allocator that may not be accessible through the malloc API linked
into the program.
Initialization
static inline vstring *
vs_init(vstring *vs, vstring_malloc *vm, enum vstring_type type, char *buf,
size_t size)
A vstring is initialized by calling vs_init with the proper arguments.
If the first argument is NULL, the vstring itself will be dynamically
allocated. It is legal to pass a pointer to a statically allocated
vstring of type VS_TYPE_DYNAMIC.
The vstring_malloc argument, if non-NULL, provides pointers to a set of
malloc, realloc, and free-compatible functions that are used to allocate
the vstring (if needed) and its underlying buffer. If this argument is
NULL, the library uses calloc, realloc, and free directly.
The buf and size arguments are useful when vs_init is called to initialize
a VS_TYPE_STATIC or VS_TYPE_GROWABLE vstring, but also allow passing in an
externally allocated buffer with a known size into a vstring of type
VS_TYPE_DYNAMIC. Note that such a buffer must have been allocated with the
same malloc available to vstring (and must therefore be the same allocator
passed through vstring_malloc, if any).
Destruction
static inline void
vs_deinit(vstring *vs)
The vs_deinit function destroys the vstring passed into it. If the vstring
is of type VS_TYPE_DYNAMIC, its contents will be freed. If the vstring
passed to vs_deinit was dynamically allocated, it will be freed as well.
In all cases, sizeof (*vs) bytes will be zeroed at the address pointed to
by the argument to vs_deinit.
Reusing vstrings
static inline void
vs_rewind(vstring *vs)
To avoid constant allocation of vstrings and their contents, it may be
useful to reuse vstring objects. Calling vs_rewind resets the pointer of
the string. Future calls to operations modifying the underlying buffer
will then start at the beginning of the buffer.
Resizing vstrings
static inline void *
vs_resize(vstring *vs, size_t hint)
Don't worry about it. This is done for you.
Appending characters
static inline bool
vs_push(vstring *vs, char c)
The vs_push function appends an individual character c into the buffer
managed by *vs. Returns true if successful, false otherwise.
This function isn't really intended to be used outside the API. If you
are using this function in a loop, you are almost certainly doing it wrong.
This function may fail if:
* The buffer is VS_TYPE_STATIC and the append would overflow the buffer.
* The buffer is not large enough to hold len bytes and resizing failed.
Appending strings
static inline bool
vs_pushstr(vstring *vs, const char *s, uint64_t len)
The vs_pushstr function appends len characters pointed to by s into the
buffer managed by *vs. Returns true if successful, false otherwise.
This function may fail if:
* The value of len is 0.
* The value of s is NULL.
* The buffer is VS_TYPE_STATIC and the append would overflow the buffer.
* The buffer is not large enough to hold len bytes and resizing failed.
Appending Integers
static inline bool
vs_pushuint(vstring *vs, uint64_t n)
static inline bool
vs_pushint(vstring *vs, int64_t n)
The vs_pushuint and vs_pushint functions append a string representation of
the integer n to the buffer pointed to by *vs. Returns true if successful,
false otherwise.
This can function fail for the same reasons as vs_push and vs_pushstr.
Appending FP Numbers
static inline bool
vs_pushdouble(vstring *vs, double n)
Stringifies the representation of n and appends that to the buffer pointed
to by *vs. It works by using modf to retrieve the whole integer part and
the fractional part. The fractional part is padded to 9 spaces for no good
reason. NAN, positive and negative infinity, and negative zero is handled.
Returns true if successful, false otherwise.
This function fails when any concatenation fails, or if the floating point
number passed in is a subnormal FP number.
Creating a C String
static inline bool
vs_finalize(vstring *vs)
To make the buffer managed by *vs a valid C string, vs_finalize must be
called. This function is a convenience wrapper around vs_push(vs, '\0')
and can fail for the same reasons.
To get the length of the resulting C string without calling strlen, you
may use vs_len(vs) - 1.
Getting the contents of a vstring
static inline char *
vs_contents(vstring *vs)
The buffer returned by vs_contents is not safe to use as a C string until
vs_finalize was successfully called.
Getting the vstring length
static inline uint64_t
vs_len(vstring *vs)
The length of the vstring-managed buffer can always be determined by a
call to vs_len.
Future Improvements
vs_pushdouble probably isn't correct. We probably want to change the API
to allow expression of the number of significant figures wanted. We may
also want to specify minimum or maximum padding values (with no padding
being an option for maximum value).
If you have additional suggestions, file an issue or send a pull request!
|