summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile18
-rw-r--r--include/conts/sp.h251
-rw-r--r--tests/sp.c134
3 files changed, 401 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index af4dbec..804e500 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
CFLAGS = -g -Wall -Wextra -O2
-check: check-vec check-spvec check-sptree check-map
+check: check-vec check-spvec check-sptree check-map check-sp
# see scripts/coverage for coverage testing
TESTITER = 1000
@@ -33,7 +33,14 @@ check-map:
deps/covsrv/src/client.c tests/map.c -o build/map
./scripts/run-test ./build/map
-bench: bench-vec bench-spvec bench-sptree bench-map
+check-sp:
+ mkdir -p build
+ $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \
+ -Iinclude -Ideps/covsrv/include \
+ deps/covsrv/src/client.c tests/sp.c -o build/sp
+ ./scripts/run-test ./build/sp
+
+bench: bench-vec bench-spvec bench-sptree bench-map bench-sp
bench-vec:
mkdir -p reports/vec
@@ -63,5 +70,12 @@ bench-map:
tests/map.c -o build/map_opt
time ./build/map_opt 2> reports/map/map_bench.txt
+bench-sp:
+ mkdir -p reports/sp
+ $(CC) $(CFLAGS) -DITER=$(BENCHITER) \
+ -Iinclude -Ideps/covsrv/include \
+ tests/sp.c -o build/sp_opt
+ time ./build/sp_opt 2> reports/sp/sp_bench.txt
+
clean:
rm -rf build
diff --git a/include/conts/sp.h b/include/conts/sp.h
new file mode 100644
index 0000000..73fcdda
--- /dev/null
+++ b/include/conts/sp.h
@@ -0,0 +1,251 @@
+#ifndef SP_H
+#define SP_H
+
+#define SP_SHORT_STR_LEN sizeof(char *)
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "conts.h"
+
+#endif /* SP_H */
+
+#ifndef SP_MALLOC
+#define SP_MALLOC malloc
+#endif
+
+#ifndef SP_REALLOC
+#define SP_REALLOC realloc
+#endif
+
+#ifndef SP_FREE
+#define SP_FREE free
+#endif
+
+#if defined(SP_NAME) || !defined(SP_DEFAULTED)
+
+#if !defined(SP_NAME)
+#define SP_NAME sp
+#define SP_DEFAULTED
+#endif
+
+#define SP(a) CONTS_JOIN(SP_NAME, a)
+#define SP_STRUCT SP_NAME
+
+struct SP_STRUCT {
+ size_t l;
+ union {
+ char *p;
+ char b[SP_SHORT_STR_LEN];
+ };
+};
+
+static inline struct SP_STRUCT SP(create)()
+{
+ struct SP_STRUCT s;
+ s.l = 0;
+ s.b[0] = '\0';
+ return s;
+}
+
+static inline void SP(destroy)(const struct SP_STRUCT *s)
+{
+ if (s->l >= SP_SHORT_STR_LEN)
+ SP_FREE(s->p);
+}
+
+static inline size_t SP(len)(const struct SP_STRUCT *s)
+{
+ return s->l;
+}
+
+static inline size_t SP(cap)(size_t n)
+{
+ size_t c = SP_SHORT_STR_LEN;
+ while (c <= n)
+ c *= 2;
+
+ return c;
+}
+
+static inline char *SP(str)(struct SP_STRUCT *s)
+{
+ if (s->l < SP_SHORT_STR_LEN)
+ return &s->b[0];
+
+ return s->p;
+}
+
+static inline char *SP(at)(struct SP_STRUCT *s, size_t n)
+{
+ /* n == s->l is allowed, but it's just the trailing \0 */
+ assert(n <= s->l);
+ if (s->l < SP_SHORT_STR_LEN)
+ return &s->b[n];
+
+ return &s->p[n];
+}
+
+static inline void SP(trunc)(struct SP_STRUCT *s)
+{
+ if (s->l < SP_SHORT_STR_LEN)
+ s->b[s->l] = '\0';
+ else
+ s->p[s->l] = '\0';
+}
+
+static inline int SP(append_strn)(struct SP_STRUCT *s, const char *buf, size_t n)
+{
+ size_t nl = s->l + n;
+
+ /* we can fit into the static buffer */
+ if (nl < SP_SHORT_STR_LEN) {
+ memcpy(s->b + s->l, buf, n);
+ s->b[nl] = '\0';
+ s->l = nl;
+ return 0;
+ }
+
+ /* we need a dynamic buffer, but the current string is in the static
+ * buffer */
+ if (s->l < SP_SHORT_STR_LEN) {
+ size_t c = SP(cap)(nl);
+ char *p = SP_MALLOC(c * sizeof(char));
+ if (!p)
+ return -1;
+
+ memcpy(p, s->b, s->l);
+ memcpy(p + s->l, buf, n);
+ p[nl] = '\0';
+
+ s->p = p;
+ s->l = nl;
+ return 0;
+ }
+
+ /* we already have a buffer, extend it if need be */
+ size_t c = SP(cap)(s->l);
+ if (c <= nl) {
+ char *p = SP_REALLOC(s->p, SP(cap)(nl));
+ if (!p)
+ return -1;
+
+ s->p = p;
+ }
+
+ memcpy(s->p + s->l, buf, n);
+ s->p[nl] = '\0';
+ return 0;
+}
+
+static inline int SP(append_str)(struct SP_STRUCT *s, const char *buf)
+{
+ return SP(append_strn)(s, buf, strlen(buf));
+}
+
+static inline int SP(append_vfmt)(struct SP_STRUCT *s, const char *fmt, va_list args)
+{
+ /* opportunistically copy into our existing buffer */
+ size_t c = SP(cap)(s->l);
+
+ va_list cp;
+ va_copy(cp, args);
+ int l = vsnprintf(SP(str)(s) + s->l, c - s->l, fmt, cp);
+ va_end(cp);
+
+ /* something went wrong, place back \0 to keep string in tact */
+ if (l < 0) {
+ SP(trunc)(s);
+ va_end(args);
+ return l;
+ }
+
+ /* everything written fit into our buffer */
+ if (s->l + l < c) {
+ s->l += l;
+ va_end(args);
+ return 0;
+ }
+
+ /* since the static buffer is always available, we end up here when we
+ * either run out of space in it or a dynamic buffer. */
+ size_t cn = SP(cap)(s->l + l);
+
+ char *p;
+ if (s->l < SP_SHORT_STR_LEN) {
+ /* If static, allocate a dynamic buffer. */
+ p = SP_MALLOC(cn * sizeof(char));
+ if (!p) {
+ SP(trunc)(s);
+ va_end(args);
+ return -1;
+ }
+
+ memcpy(p, s->b, s->l);
+ } else {
+ /* dynamic buffer, resize */
+ p = SP_REALLOC(s->p, cn * sizeof(char));
+ if (!p) {
+ SP(trunc)(s);
+ va_end(args);
+ return -1;
+ }
+ }
+
+ int ln = vsnprintf(p + s->l, cn - s->l, fmt, args);
+ va_end(args);
+ assert(ln == l);
+ s->p = p;
+ s->l += l;
+ return 0;
+}
+
+static inline int SP(append_fmt)(struct SP_STRUCT *s, const char *fmt, ...)
+{
+ int r = 0;
+ va_list args;
+ va_start(args, fmt);
+ r = SP(append_vfmt)(s, fmt, args);
+ va_end(args);
+ return r;
+}
+
+typedef struct {
+ size_t i;
+ char *p;
+} SP(iter);
+
+static inline SP(iter) SP(begin)(struct SP_STRUCT *s)
+{
+ size_t i = 0;
+ return (SP(iter)){
+ .i = i,
+ .p = SP(at)(s, i)
+ };
+}
+
+static inline int SP(end)(struct SP_STRUCT *s, SP(iter) iter)
+{
+ return iter.i >= s->l;
+}
+
+static inline SP(iter) SP(next)(struct SP_STRUCT *s, SP(iter) iter)
+{
+ size_t n = iter.i + 1;
+ return (SP(iter)){
+ .i = n,
+ .p = SP(at)(s, n)
+ };
+}
+
+#endif /* !defined(SP_DEFAULT_NAME) && SP_NAME != sp */
+
+#undef SP_STRUCT
+#undef SP_REALLOC
+#undef SP_MALLOC
+#undef SP_FREE
+#undef SP_NAME
+#undef SP
+
+/* purposefully let SP_DEFAULTED by */
diff --git a/tests/sp.c b/tests/sp.c
new file mode 100644
index 0000000..369e376
--- /dev/null
+++ b/tests/sp.c
@@ -0,0 +1,134 @@
+#include "test.h"
+
+/* all optional */
+#define SP_MALLOC mallocc
+#define SP_REALLOC reallocc
+#define SP_FREE free
+#include <conts/sp.h>
+
+/* including default name is fine */
+#include <conts/sp.h>
+
+/* separate string type, can be defined in addition to the default type. The
+ * default type can be included multiple times, but specially-named types
+ * cannot. */
+#define SP_NAME second
+#define SP_MALLOC mallocc
+#define SP_REALLOC reallocc
+#define SP_FREE free
+#include <conts/sp.h>
+
+int main()
+{
+#if defined(COVERAGE)
+ assert(!covsrv_init());
+ atexit(covsrv_destroy);
+#endif
+
+ struct sp sp = sp_create();
+
+ /* should be in static buffer, cannot fail
+ * (though the user should probably assume that appending can always
+ * fail) */
+ assert(!sp_append_str(&sp, "123"));
+
+ /* don't use sp.b directly, prefer sp_str, but this is fine for testing */
+ assert(strcmp(sp.b, "123") == 0);
+
+ /* test static iteration */
+ foreach(sp, c, &sp) {
+ switch (c.i) {
+ case 0: assert(*c.p == '1'); break;
+ case 1: assert(*c.p == '2'); break;
+ case 2: assert(*c.p == '3'); break;
+ default: assert(0 && "weird iteration");
+ }
+ }
+
+ /* now we should switch over to the dynamic buffer, as we have more
+ * characters than can fit in any regular pointer type (3 for 32bit, 7
+ * for 64 bit, 15 for stuff like Cheri), might fail */
+ if (sp_append_str(&sp, "4567890ABCDEFG")) {
+ fprintf(stderr, "failed appending to string buffer");
+ sp_destroy(&sp);
+ return -1;
+ }
+
+ /* same deal, check that we're using the dynamic buffer */
+ assert(strcmp(sp.p, "1234567890ABCDEFG") == 0);
+
+ /* test dynamic iteration */
+ foreach(sp, c, &sp) {
+ switch (c.i) {
+ case 0: assert(*c.p == '1'); break;
+ case 1: assert(*c.p == '2'); break;
+ case 2: assert(*c.p == '3'); break;
+ case 3: assert(*c.p == '4'); break;
+ case 4: assert(*c.p == '5'); break;
+ case 5: assert(*c.p == '6'); break;
+ case 6: assert(*c.p == '7'); break;
+ case 7: assert(*c.p == '8'); break;
+ case 8: assert(*c.p == '9'); break;
+ case 9: assert(*c.p == '0'); break;
+ case 10: assert(*c.p == 'A'); break;
+ case 11: assert(*c.p == 'B'); break;
+ case 12: assert(*c.p == 'C'); break;
+ case 13: assert(*c.p == 'D'); break;
+ case 14: assert(*c.p == 'E'); break;
+ case 15: assert(*c.p == 'F'); break;
+ case 16: assert(*c.p == 'G'); break;
+ case 17: assert(*c.p == 'H'); break;
+ default: assert(0 && "weird iteration");
+ }
+ }
+
+ /* 'reset' string */
+ sp_destroy(&sp);
+
+ /* test formatting functions */
+ sp = sp_create();
+
+ /* static formatting */
+ for (int i = 1; i < 4; ++i)
+ assert(!sp_append_fmt(&sp, "%d", i));
+
+ assert(strcmp(sp.b, "123") == 0);
+
+ /* dynamic formatting */
+ for (int i = 4; i < 15; ++i)
+ if (sp_append_fmt(&sp, "%d", i)) {
+ fprintf(stderr, "failed formatting input\n");
+ sp_destroy(&sp);
+ return -1;
+ }
+
+ assert(strcmp(sp.p, "1234567891011121314") == 0);
+ sp_destroy(&sp);
+
+ /* do some benchmarking, oh yeah! */
+ struct second sec = second_create();
+ for (int i = 0; i < ITER; ++i)
+ if (second_append_str(&sec, "BENCHMARK, FUCK YEAH\n")) {
+ fprintf(stderr, "failed when stressing append\n");
+ second_destroy(&sec);
+ return -1;
+ }
+
+ /* hmm, come to think of it, foreach_line or something along those lines
+ * might be pretty neat? */
+
+ second_destroy(&sec);
+
+ sec = second_create();
+ for (int i = 0; i < ITER; ++i)
+ if (second_append_fmt(&sec, "I'm in iteration %d!\n", i)) {
+ fprintf(stderr, "failed when stressing fmt\n");
+ second_destroy(&sec);
+ return -1;
+ }
+
+ second_destroy(&sec);
+
+ /* all done! */
+ return 0;
+}