diff options
| -rw-r--r-- | Makefile | 18 | ||||
| -rw-r--r-- | include/conts/sp.h | 251 | ||||
| -rw-r--r-- | tests/sp.c | 134 |
3 files changed, 401 insertions, 2 deletions
@@ -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; +} |
