diff --git a/FU.xs b/FU.xs index 9b9d17c..9392677 100644 --- a/FU.xs +++ b/FU.xs @@ -10,15 +10,10 @@ MODULE = FU PACKAGE = FU::XS PROTOTYPES: DISABLE -SV *json_format(val) - SV *val - PREINIT: - SV *r; - fustr buf = {}; +void json_format(SV *val) CODE: - fujson_fmt(&buf, val); - r = fustr_sv(&buf); - SvUTF8_on(r); - RETVAL = r; - OUTPUT: - RETVAL + fustr buf; + fustr_init(&buf, 128); + fujson_fmt(aTHX_ &buf, val); + ST(0) = fustr_done(&buf); + SvUTF8_on(ST(0)); diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 5e967b4..4fbd5a2 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -1,6 +1,8 @@ ~($|/) (^|/)\. ^MANIFEST\.bak +^Makefile\.old +^Makefile$ ^pm_to_blib ^blib/ ^FU-[^/]+/ diff --git a/bench.PL b/bench.PL index 06e49da..9d95c83 100755 --- a/bench.PL +++ b/bench.PL @@ -1,5 +1,7 @@ #!/usr/bin/perl +exit if @ARGV && @ARGV[0] eq 'bench'; + # Can be invoked as: # ./bench.PL # (or 'make bench') generates FU/Benchmarks.pod # ./bench.PL regex # run benchmark(s) matching the regex diff --git a/c/common.c b/c/common.c index c3f0ac8..37a6961 100644 --- a/c/common.c +++ b/c/common.c @@ -1,43 +1,57 @@ -/* Custom string builder, comparable to functionality provided by SV* - * functions, but with less magic and better inlineable. */ +/* Custom string builder, should be slightly faster than using Sv* macros directly. */ typedef struct { - size_t len; - size_t size; - char *buf; + SV *sv; + char *cur; + char *end; } fustr; -/* No need to call this, an empty fustr is already usable. - * This allows setting a custom initial size. */ -static void fustr_init(fustr *s, size_t prealloc) { - s->len = 0; - s->size = prealloc; - s->buf = safemalloc(prealloc); +static void fustr_init_(pTHX_ fustr *s, size_t prealloc) { + s->sv = sv_2mortal(newSV(prealloc)); + SvPOK_only(s->sv); + s->cur = SvPVX(s->sv); + s->end = SvEND(s->sv); } -static void fustr_grow(fustr *s, size_t add) { - if (s->size == 0) s->size = 512; - while (s->size < s->len + add) - s->size *= 2; - s->buf = saferealloc(s->buf, s->size); +static void fustr_grow(pTHX_ fustr *s, size_t add) { + size_t off = s->cur - SvPVX(s->sv); + size_t newlen = 64; + add += off; + /* Increment to next power of two; SvGROW's default strategy is slow */ + while (newlen < add) newlen <<= 1; + char *buf = SvGROW(s->sv, newlen); + s->cur = buf + off; + s->end = buf + SvLEN(s->sv); } -#define fustr_reserve(s, n) do {\ - if (UNLIKELY((s)->size < (s)->len + (n))) fustr_grow(s, n);\ - } while(0) +static inline void fustr_reserve_(pTHX_ fustr *s, size_t add) { + if (UNLIKELY(s->end < s->cur + add)) fustr_grow(aTHX_ s, add); +} -#define fustr_write(s, str, n) do {\ - fustr_reserve(s, n);\ - memcpy((s)->buf+(s)->len, str, (n));\ - (s)->len += (n);\ - } while(0) +static inline void fustr_write_(pTHX_ fustr *s, const char *str, size_t n) { + fustr_reserve_(aTHX_ s, n); + memcpy(s->cur, str, n); + s->cur += n; +} -/* Move the string buffer into a new SV; fustr should be considered invalid after this call. - * Does not set the UTF8 flag. */ -static SV *fustr_sv(fustr *s) { - SV *r = newSV(0); - fustr_write(s, "", 1); // trailing nul - sv_usepvn_flags(r, s->buf, s->len-1, SV_HAS_TRAILING_NUL); +/* Adds n uninitialized bytes to the string and returns a buffer to write the data to */ +static inline char *fustr_write_buf_(pTHX_ fustr *s, size_t n) { + fustr_reserve_(aTHX_ s, n); + char *buf = s->cur; + s->cur += n; + return buf; +} + +static SV *fustr_done_(pTHX_ fustr *s) { + fustr_reserve_(aTHX_ s, 1); + *s->cur = 0; + SvCUR_set(s->sv, s->cur - SvPVX(s->sv)); // TODO: SvPV_shrink_to_cur? - return r; + return s->sv; } + +#define fustr_init(a,b) fustr_init_(aTHX_ a,b) +#define fustr_reserve(a,b) fustr_reserve_(aTHX_ a,b) +#define fustr_write(a,b,c) fustr_write_(aTHX_ a,b,c) +#define fustr_write_buf(a,b) fustr_write_buf_(aTHX_ a,b) +#define fustr_done(a) fustr_done_(aTHX_ a) diff --git a/c/jsonfmt.c b/c/jsonfmt.c index 0285c57..051a2e5 100644 --- a/c/jsonfmt.c +++ b/c/jsonfmt.c @@ -1,8 +1,9 @@ -static void fujson_fmt(fustr *, SV *); +static void fujson_fmt(pTHX_ fustr *, SV *); -static void fujson_fmt_str(fustr *out, const char *stri, size_t len, int utf8) { +static void fujson_fmt_str(pTHX_ fustr *out, const char *stri, size_t len, int utf8) { size_t off = 0, loff; const unsigned char *str = (const unsigned char *)stri; + unsigned char *buf; unsigned char x = 0; /* Validate entire string for conformance if this is flagged as a utf8 @@ -35,7 +36,7 @@ static void fujson_fmt_str(fustr *out, const char *stri, size_t len, int utf8) { off++; } } - fustr_write(out, str+loff, off-loff); + fustr_write(out, (char *)str+loff, off-loff); if (off < len) { /* early break, which means current byte needs special processing */ switch (x) { @@ -48,16 +49,14 @@ static void fujson_fmt_str(fustr *out, const char *stri, size_t len, int utf8) { case 0x0d: fustr_write(out, "\\r", 2); break; default: if (x < 0x80) { - fustr_reserve(out, 6); - memcpy(out->buf+out->len, "\\u00", 4); - out->buf[out->len+4] = PL_hexdigit[(x >> 4) & 0x0f]; - out->buf[out->len+5] = PL_hexdigit[x & 0x0f]; - out->len += 6; + buf = (unsigned char *)fustr_write_buf(out, 6); + memcpy(buf, "\\u00", 4); + buf[4] = PL_hexdigit[(x >> 4) & 0x0f]; + buf[5] = PL_hexdigit[x & 0x0f]; } else { /* x >= 0x80, !utf8, so encode as 2-byte UTF-8 */ - fustr_reserve(out, 2); - out->buf[out->len ] = 0xc0 | (x >> 6); - out->buf[out->len+1] = 0x80 | (x & 0x3f); - out->len += 2; + buf = (unsigned char *)fustr_write_buf(out, 2); + buf[0] = 0xc0 | (x >> 6); + buf[1] = 0x80 | (x & 0x3f); } } off++; @@ -80,7 +79,7 @@ static const char fujson_digits[] = "80818283848586878889" "90919293949596979899"; -static void fujson_fmt_int(fustr *out, SV *val) { +static void fujson_fmt_int(pTHX_ fustr *out, SV *val) { char buf[32]; char *r = buf+31; int neg = 0; @@ -111,19 +110,19 @@ static void fujson_fmt_int(fustr *out, SV *val) { fustr_write(out, r, uv); } -static void fujson_fmt_av(fustr *out, AV *av) { +static void fujson_fmt_av(pTHX_ fustr *out, AV *av) { int i, len = av_count(av); fustr_write(out, "[", 1); for (i=0; ibuf + out->len); - out->len += strlen(out->buf + out->len); + Gconvert(nv, NV_DIG, 0, out->cur); + out->cur += strlen(out->cur); } else if (SvIOKp(val)) { - fujson_fmt_int(out, val); + fujson_fmt_int(aTHX_ out, val); } else if (SvROK(val)) { SV *rv = SvRV(val); SvGETMAGIC(rv); if (UNLIKELY(SvOBJECT(rv))) { /* TODO: Check for TO_JSON */ } - else if (SvTYPE(rv) == SVt_PVHV) fujson_fmt_hv(out, (HV *)rv); - else if (SvTYPE(rv) == SVt_PVAV) fujson_fmt_av(out, (AV *)rv); + else if (SvTYPE(rv) == SVt_PVHV) fujson_fmt_hv(aTHX_ out, (HV *)rv); + else if (SvTYPE(rv) == SVt_PVAV) fujson_fmt_av(aTHX_ out, (AV *)rv); else return; /* TODO: error */ } else if (!SvOK(val)) { fustr_write(out, "null", 4); @@ -178,4 +176,3 @@ static void fujson_fmt(fustr *out, SV *val) { /* TODO: canonical */ /* TODO: pretty */ /* TODO: max depth? */ -/* TODO: threading support */