diff --git a/buffer-builder.cabal b/buffer-builder.cabal
--- a/buffer-builder.cabal
+++ b/buffer-builder.cabal
@@ -1,5 +1,5 @@
 name:                buffer-builder
-version:             0.2.4.6
+version:             0.2.4.7
 synopsis:            Library for efficiently building up buffers, one piece at a time
 description:
 
@@ -70,10 +70,9 @@
   --ghc-options: -ddump-ds -ddump-simpl -ddump-stg -ddump-opt-cmm -ddump-asm -ddump-to-file
 
   hs-source-dirs: src
-  c-sources: cbits/buffer.cpp cbits/branchlut.cpp
+  c-sources: cbits/buffer.c cbits/branchlut.c
   install-includes: cbits/branchlut.h
-  cc-options: -O2 -Wall
-  extra-libraries: stdc++
+  cc-options: -O2 -Wall -Werror -std=c99
 
 test-suite tests
   type: exitcode-stdio-1.0
diff --git a/cbits/branchlut.c b/cbits/branchlut.c
new file mode 100644
--- /dev/null
+++ b/cbits/branchlut.c
@@ -0,0 +1,271 @@
+/** <https://github.com/miloyip/itoa-benchmark/blob/940542a7770155ee3e9f2777ebc178dc899b43e0/src/branchlut.cpp>
+ */
+
+#include <string.h>
+#include <stdint.h>
+
+static const char gDigitsLut[200] = {
+    '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9',
+    '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9',
+    '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9',
+    '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9',
+    '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9',
+    '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9',
+    '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9',
+    '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9',
+    '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9',
+    '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9'
+};
+
+// Branching for different cases (forward)
+// Use lookup table of two digits
+
+size_t u32toa_branchlut(uint32_t value, char* buffer) {
+    char* start = buffer;
+
+    if (value < 10000) {
+        const uint32_t d1 = (value / 100) << 1;
+        const uint32_t d2 = (value % 100) << 1;
+
+        if (value >= 1000)
+            *buffer++ = gDigitsLut[d1];
+        if (value >= 100)
+            *buffer++ = gDigitsLut[d1 + 1];
+        if (value >= 10)
+            *buffer++ = gDigitsLut[d2];
+        *buffer++ = gDigitsLut[d2 + 1];
+    }
+    else if (value < 100000000) {
+        // value = bbbbcccc
+        const uint32_t b = value / 10000;
+        const uint32_t c = value % 10000;
+
+        const uint32_t d1 = (b / 100) << 1;
+        const uint32_t d2 = (b % 100) << 1;
+
+        const uint32_t d3 = (c / 100) << 1;
+        const uint32_t d4 = (c % 100) << 1;
+
+        if (value >= 10000000)
+            *buffer++ = gDigitsLut[d1];
+        if (value >= 1000000)
+            *buffer++ = gDigitsLut[d1 + 1];
+        if (value >= 100000)
+            *buffer++ = gDigitsLut[d2];
+        *buffer++ = gDigitsLut[d2 + 1];
+
+        *buffer++ = gDigitsLut[d3];
+        *buffer++ = gDigitsLut[d3 + 1];
+        *buffer++ = gDigitsLut[d4];
+        *buffer++ = gDigitsLut[d4 + 1];
+    }
+    else {
+        // value = aabbbbcccc in decimal
+
+        const uint32_t a = value / 100000000; // 1 to 42
+        value %= 100000000;
+
+        if (a >= 10) {
+            const unsigned i = a << 1;
+            *buffer++ = gDigitsLut[i];
+            *buffer++ = gDigitsLut[i + 1];
+        }
+        else
+            *buffer++ = '0' + a;
+
+        const uint32_t b = value / 10000; // 0 to 9999
+        const uint32_t c = value % 10000; // 0 to 9999
+
+        const uint32_t d1 = (b / 100) << 1;
+        const uint32_t d2 = (b % 100) << 1;
+
+        const uint32_t d3 = (c / 100) << 1;
+        const uint32_t d4 = (c % 100) << 1;
+
+        *buffer++ = gDigitsLut[d1];
+        *buffer++ = gDigitsLut[d1 + 1];
+        *buffer++ = gDigitsLut[d2];
+        *buffer++ = gDigitsLut[d2 + 1];
+        *buffer++ = gDigitsLut[d3];
+        *buffer++ = gDigitsLut[d3 + 1];
+        *buffer++ = gDigitsLut[d4];
+        *buffer++ = gDigitsLut[d4 + 1];
+    }
+    return buffer - start;
+}
+
+size_t i32toa_branchlut(int32_t value, char* buffer) {
+    if (value < 0) {
+        *buffer++ = '-';
+        return 1 + u32toa_branchlut(~(uint32_t)(value) + 1u, buffer);
+    } else {
+        return u32toa_branchlut((uint32_t)(value), buffer);
+    }
+}
+
+size_t u64toa_branchlut(uint64_t value, char* buffer) {
+    char* start = buffer;
+    if (value < 100000000) {
+        uint32_t v = (uint32_t)value;
+        if (v < 10000) {
+            const uint32_t d1 = (v / 100) << 1;
+            const uint32_t d2 = (v % 100) << 1;
+
+            if (v >= 1000)
+                *buffer++ = gDigitsLut[d1];
+            if (v >= 100)
+                *buffer++ = gDigitsLut[d1 + 1];
+            if (v >= 10)
+                *buffer++ = gDigitsLut[d2];
+            *buffer++ = gDigitsLut[d2 + 1];
+        }
+        else {
+            // value = bbbbcccc
+            const uint32_t b = v / 10000;
+            const uint32_t c = v % 10000;
+
+            const uint32_t d1 = (b / 100) << 1;
+            const uint32_t d2 = (b % 100) << 1;
+
+            const uint32_t d3 = (c / 100) << 1;
+            const uint32_t d4 = (c % 100) << 1;
+
+            if (value >= 10000000)
+                *buffer++ = gDigitsLut[d1];
+            if (value >= 1000000)
+                *buffer++ = gDigitsLut[d1 + 1];
+            if (value >= 100000)
+                *buffer++ = gDigitsLut[d2];
+            *buffer++ = gDigitsLut[d2 + 1];
+
+            *buffer++ = gDigitsLut[d3];
+            *buffer++ = gDigitsLut[d3 + 1];
+            *buffer++ = gDigitsLut[d4];
+            *buffer++ = gDigitsLut[d4 + 1];
+        }
+    }
+    else if (value < 10000000000000000) {
+        const uint32_t v0 = (uint32_t)(value / 100000000);
+        const uint32_t v1 = (uint32_t)(value % 100000000);
+
+        const uint32_t b0 = v0 / 10000;
+        const uint32_t c0 = v0 % 10000;
+
+        const uint32_t d1 = (b0 / 100) << 1;
+        const uint32_t d2 = (b0 % 100) << 1;
+
+        const uint32_t d3 = (c0 / 100) << 1;
+        const uint32_t d4 = (c0 % 100) << 1;
+
+        const uint32_t b1 = v1 / 10000;
+        const uint32_t c1 = v1 % 10000;
+
+        const uint32_t d5 = (b1 / 100) << 1;
+        const uint32_t d6 = (b1 % 100) << 1;
+
+        const uint32_t d7 = (c1 / 100) << 1;
+        const uint32_t d8 = (c1 % 100) << 1;
+
+        if (value >= 1000000000000000)
+            *buffer++ = gDigitsLut[d1];
+        if (value >= 100000000000000)
+            *buffer++ = gDigitsLut[d1 + 1];
+        if (value >= 10000000000000)
+            *buffer++ = gDigitsLut[d2];
+        if (value >= 1000000000000)
+            *buffer++ = gDigitsLut[d2 + 1];
+        if (value >= 100000000000)
+            *buffer++ = gDigitsLut[d3];
+        if (value >= 10000000000)
+            *buffer++ = gDigitsLut[d3 + 1];
+        if (value >= 1000000000)
+            *buffer++ = gDigitsLut[d4];
+        if (value >= 100000000)
+            *buffer++ = gDigitsLut[d4 + 1];
+
+        *buffer++ = gDigitsLut[d5];
+        *buffer++ = gDigitsLut[d5 + 1];
+        *buffer++ = gDigitsLut[d6];
+        *buffer++ = gDigitsLut[d6 + 1];
+        *buffer++ = gDigitsLut[d7];
+        *buffer++ = gDigitsLut[d7 + 1];
+        *buffer++ = gDigitsLut[d8];
+        *buffer++ = gDigitsLut[d8 + 1];
+    }
+    else {
+        const uint32_t a = (uint32_t)(value / 10000000000000000); // 1 to 1844
+        value %= 10000000000000000;
+
+        if (a < 10)
+            *buffer++ = '0' + a;
+        else if (a < 100) {
+            const uint32_t i = a << 1;
+            *buffer++ = gDigitsLut[i];
+            *buffer++ = gDigitsLut[i + 1];
+        }
+        else if (a < 1000) {
+            *buffer++ = '0' + a / 100;
+
+            const uint32_t i = (a % 100) << 1;
+            *buffer++ = gDigitsLut[i];
+            *buffer++ = gDigitsLut[i + 1];
+        }
+        else {
+            const uint32_t i = (a / 100) << 1;
+            const uint32_t j = (a % 100) << 1;
+            *buffer++ = gDigitsLut[i];
+            *buffer++ = gDigitsLut[i + 1];
+            *buffer++ = gDigitsLut[j];
+            *buffer++ = gDigitsLut[j + 1];
+        }
+
+        const uint32_t v0 = (uint32_t)(value / 100000000);
+        const uint32_t v1 = (uint32_t)(value % 100000000);
+
+        const uint32_t b0 = v0 / 10000;
+        const uint32_t c0 = v0 % 10000;
+
+        const uint32_t d1 = (b0 / 100) << 1;
+        const uint32_t d2 = (b0 % 100) << 1;
+
+        const uint32_t d3 = (c0 / 100) << 1;
+        const uint32_t d4 = (c0 % 100) << 1;
+
+        const uint32_t b1 = v1 / 10000;
+        const uint32_t c1 = v1 % 10000;
+
+        const uint32_t d5 = (b1 / 100) << 1;
+        const uint32_t d6 = (b1 % 100) << 1;
+
+        const uint32_t d7 = (c1 / 100) << 1;
+        const uint32_t d8 = (c1 % 100) << 1;
+
+        *buffer++ = gDigitsLut[d1];
+        *buffer++ = gDigitsLut[d1 + 1];
+        *buffer++ = gDigitsLut[d2];
+        *buffer++ = gDigitsLut[d2 + 1];
+        *buffer++ = gDigitsLut[d3];
+        *buffer++ = gDigitsLut[d3 + 1];
+        *buffer++ = gDigitsLut[d4];
+        *buffer++ = gDigitsLut[d4 + 1];
+        *buffer++ = gDigitsLut[d5];
+        *buffer++ = gDigitsLut[d5 + 1];
+        *buffer++ = gDigitsLut[d6];
+        *buffer++ = gDigitsLut[d6 + 1];
+        *buffer++ = gDigitsLut[d7];
+        *buffer++ = gDigitsLut[d7 + 1];
+        *buffer++ = gDigitsLut[d8];
+        *buffer++ = gDigitsLut[d8 + 1];
+    }
+
+    return buffer - start;
+}
+
+size_t i64toa_branchlut(int64_t value, char* buffer) {
+    if (value < 0) {
+        *buffer++ = '-';
+        return 1 + u64toa_branchlut(~(uint64_t)(value) + 1u, buffer);
+    } else {
+        return u64toa_branchlut((uint64_t)(value), buffer);
+    }
+}
diff --git a/cbits/branchlut.cpp b/cbits/branchlut.cpp
deleted file mode 100644
--- a/cbits/branchlut.cpp
+++ /dev/null
@@ -1,271 +0,0 @@
-/** <https://github.com/miloyip/itoa-benchmark/blob/940542a7770155ee3e9f2777ebc178dc899b43e0/src/branchlut.cpp>
- */
-
-#include <string.h>
-#include <stdint.h>
-
-static const char gDigitsLut[200] = {
-    '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9',
-    '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9',
-    '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9',
-    '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9',
-    '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9',
-    '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9',
-    '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9',
-    '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9',
-    '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9',
-    '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9'
-};
-
-// Branching for different cases (forward)
-// Use lookup table of two digits
-
-size_t u32toa_branchlut(uint32_t value, char* buffer) {
-    char* start = buffer;
-
-    if (value < 10000) {
-        const uint32_t d1 = (value / 100) << 1;
-        const uint32_t d2 = (value % 100) << 1;
-        
-        if (value >= 1000)
-            *buffer++ = gDigitsLut[d1];
-        if (value >= 100)
-            *buffer++ = gDigitsLut[d1 + 1];
-        if (value >= 10)
-            *buffer++ = gDigitsLut[d2];
-        *buffer++ = gDigitsLut[d2 + 1];
-    }
-    else if (value < 100000000) {
-        // value = bbbbcccc
-        const uint32_t b = value / 10000;
-        const uint32_t c = value % 10000;
-        
-        const uint32_t d1 = (b / 100) << 1;
-        const uint32_t d2 = (b % 100) << 1;
-        
-        const uint32_t d3 = (c / 100) << 1;
-        const uint32_t d4 = (c % 100) << 1;
-        
-        if (value >= 10000000)
-            *buffer++ = gDigitsLut[d1];
-        if (value >= 1000000)
-            *buffer++ = gDigitsLut[d1 + 1];
-        if (value >= 100000)
-            *buffer++ = gDigitsLut[d2];
-        *buffer++ = gDigitsLut[d2 + 1];
-        
-        *buffer++ = gDigitsLut[d3];
-        *buffer++ = gDigitsLut[d3 + 1];
-        *buffer++ = gDigitsLut[d4];
-        *buffer++ = gDigitsLut[d4 + 1];
-    }
-    else {
-        // value = aabbbbcccc in decimal
-        
-        const uint32_t a = value / 100000000; // 1 to 42
-        value %= 100000000;
-        
-        if (a >= 10) {
-            const unsigned i = a << 1;
-            *buffer++ = gDigitsLut[i];
-            *buffer++ = gDigitsLut[i + 1];
-        }
-        else
-            *buffer++ = '0' + static_cast<char>(a);
-
-        const uint32_t b = value / 10000; // 0 to 9999
-        const uint32_t c = value % 10000; // 0 to 9999
-        
-        const uint32_t d1 = (b / 100) << 1;
-        const uint32_t d2 = (b % 100) << 1;
-        
-        const uint32_t d3 = (c / 100) << 1;
-        const uint32_t d4 = (c % 100) << 1;
-        
-        *buffer++ = gDigitsLut[d1];
-        *buffer++ = gDigitsLut[d1 + 1];
-        *buffer++ = gDigitsLut[d2];
-        *buffer++ = gDigitsLut[d2 + 1];
-        *buffer++ = gDigitsLut[d3];
-        *buffer++ = gDigitsLut[d3 + 1];
-        *buffer++ = gDigitsLut[d4];
-        *buffer++ = gDigitsLut[d4 + 1];
-    }
-    return buffer - start;
-}
-
-size_t i32toa_branchlut(int32_t value, char* buffer) {
-    if (value < 0) {
-        *buffer++ = '-';
-        return 1 + u32toa_branchlut(~static_cast<uint32_t>(value) + 1u, buffer);
-    } else {
-        return u32toa_branchlut(static_cast<uint32_t>(value), buffer);
-    }
-}
-
-size_t u64toa_branchlut(uint64_t value, char* buffer) {
-    char* start = buffer;
-    if (value < 100000000) {
-        uint32_t v = static_cast<uint32_t>(value);
-        if (v < 10000) {
-            const uint32_t d1 = (v / 100) << 1;
-            const uint32_t d2 = (v % 100) << 1;
-            
-            if (v >= 1000)
-                *buffer++ = gDigitsLut[d1];
-            if (v >= 100)
-                *buffer++ = gDigitsLut[d1 + 1];
-            if (v >= 10)
-                *buffer++ = gDigitsLut[d2];
-            *buffer++ = gDigitsLut[d2 + 1];
-        }
-        else {
-            // value = bbbbcccc
-            const uint32_t b = v / 10000;
-            const uint32_t c = v % 10000;
-            
-            const uint32_t d1 = (b / 100) << 1;
-            const uint32_t d2 = (b % 100) << 1;
-            
-            const uint32_t d3 = (c / 100) << 1;
-            const uint32_t d4 = (c % 100) << 1;
-            
-            if (value >= 10000000)
-                *buffer++ = gDigitsLut[d1];
-            if (value >= 1000000)
-                *buffer++ = gDigitsLut[d1 + 1];
-            if (value >= 100000)
-                *buffer++ = gDigitsLut[d2];
-            *buffer++ = gDigitsLut[d2 + 1];
-            
-            *buffer++ = gDigitsLut[d3];
-            *buffer++ = gDigitsLut[d3 + 1];
-            *buffer++ = gDigitsLut[d4];
-            *buffer++ = gDigitsLut[d4 + 1];
-        }
-    }
-    else if (value < 10000000000000000) {
-        const uint32_t v0 = static_cast<uint32_t>(value / 100000000);
-        const uint32_t v1 = static_cast<uint32_t>(value % 100000000);
-        
-        const uint32_t b0 = v0 / 10000;
-        const uint32_t c0 = v0 % 10000;
-        
-        const uint32_t d1 = (b0 / 100) << 1;
-        const uint32_t d2 = (b0 % 100) << 1;
-        
-        const uint32_t d3 = (c0 / 100) << 1;
-        const uint32_t d4 = (c0 % 100) << 1;
-
-        const uint32_t b1 = v1 / 10000;
-        const uint32_t c1 = v1 % 10000;
-        
-        const uint32_t d5 = (b1 / 100) << 1;
-        const uint32_t d6 = (b1 % 100) << 1;
-        
-        const uint32_t d7 = (c1 / 100) << 1;
-        const uint32_t d8 = (c1 % 100) << 1;
-
-        if (value >= 1000000000000000)
-            *buffer++ = gDigitsLut[d1];
-        if (value >= 100000000000000)
-            *buffer++ = gDigitsLut[d1 + 1];
-        if (value >= 10000000000000)
-            *buffer++ = gDigitsLut[d2];
-        if (value >= 1000000000000)
-            *buffer++ = gDigitsLut[d2 + 1];
-        if (value >= 100000000000)
-            *buffer++ = gDigitsLut[d3];
-        if (value >= 10000000000)
-            *buffer++ = gDigitsLut[d3 + 1];
-        if (value >= 1000000000)
-            *buffer++ = gDigitsLut[d4];
-        if (value >= 100000000)
-            *buffer++ = gDigitsLut[d4 + 1];
-        
-        *buffer++ = gDigitsLut[d5];
-        *buffer++ = gDigitsLut[d5 + 1];
-        *buffer++ = gDigitsLut[d6];
-        *buffer++ = gDigitsLut[d6 + 1];
-        *buffer++ = gDigitsLut[d7];
-        *buffer++ = gDigitsLut[d7 + 1];
-        *buffer++ = gDigitsLut[d8];
-        *buffer++ = gDigitsLut[d8 + 1];
-    }
-    else {
-        const uint32_t a = static_cast<uint32_t>(value / 10000000000000000); // 1 to 1844
-        value %= 10000000000000000;
-        
-        if (a < 10)
-            *buffer++ = '0' + static_cast<char>(a);
-        else if (a < 100) {
-            const uint32_t i = a << 1;
-            *buffer++ = gDigitsLut[i];
-            *buffer++ = gDigitsLut[i + 1];
-        }
-        else if (a < 1000) {
-            *buffer++ = '0' + static_cast<char>(a / 100);
-            
-            const uint32_t i = (a % 100) << 1;
-            *buffer++ = gDigitsLut[i];
-            *buffer++ = gDigitsLut[i + 1];
-        }
-        else {
-            const uint32_t i = (a / 100) << 1;
-            const uint32_t j = (a % 100) << 1;
-            *buffer++ = gDigitsLut[i];
-            *buffer++ = gDigitsLut[i + 1];
-            *buffer++ = gDigitsLut[j];
-            *buffer++ = gDigitsLut[j + 1];
-        }
-        
-        const uint32_t v0 = static_cast<uint32_t>(value / 100000000);
-        const uint32_t v1 = static_cast<uint32_t>(value % 100000000);
-        
-        const uint32_t b0 = v0 / 10000;
-        const uint32_t c0 = v0 % 10000;
-        
-        const uint32_t d1 = (b0 / 100) << 1;
-        const uint32_t d2 = (b0 % 100) << 1;
-        
-        const uint32_t d3 = (c0 / 100) << 1;
-        const uint32_t d4 = (c0 % 100) << 1;
-        
-        const uint32_t b1 = v1 / 10000;
-        const uint32_t c1 = v1 % 10000;
-        
-        const uint32_t d5 = (b1 / 100) << 1;
-        const uint32_t d6 = (b1 % 100) << 1;
-        
-        const uint32_t d7 = (c1 / 100) << 1;
-        const uint32_t d8 = (c1 % 100) << 1;
-        
-        *buffer++ = gDigitsLut[d1];
-        *buffer++ = gDigitsLut[d1 + 1];
-        *buffer++ = gDigitsLut[d2];
-        *buffer++ = gDigitsLut[d2 + 1];
-        *buffer++ = gDigitsLut[d3];
-        *buffer++ = gDigitsLut[d3 + 1];
-        *buffer++ = gDigitsLut[d4];
-        *buffer++ = gDigitsLut[d4 + 1];
-        *buffer++ = gDigitsLut[d5];
-        *buffer++ = gDigitsLut[d5 + 1];
-        *buffer++ = gDigitsLut[d6];
-        *buffer++ = gDigitsLut[d6 + 1];
-        *buffer++ = gDigitsLut[d7];
-        *buffer++ = gDigitsLut[d7 + 1];
-        *buffer++ = gDigitsLut[d8];
-        *buffer++ = gDigitsLut[d8 + 1];
-    }
-    
-    return buffer - start;
-}
-
-size_t i64toa_branchlut(int64_t value, char* buffer) {
-    if (value < 0) {
-        *buffer++ = '-';
-        return 1 + u64toa_branchlut(~static_cast<uint64_t>(value) + 1u, buffer);
-    } else {
-        return u64toa_branchlut(static_cast<uint64_t>(value), buffer);
-    }
-}
diff --git a/cbits/buffer.c b/cbits/buffer.c
new file mode 100644
--- /dev/null
+++ b/cbits/buffer.c
@@ -0,0 +1,492 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <assert.h>
+
+#include "branchlut.h"
+
+/*
+ * Append utf8, ASCII7, raw bytes, utf16
+ * JSON encode utf8, ascii7, utf16
+ */
+
+#if defined(__clang__) || defined(__GNUC__)
+  #define BW_NOINLINE __attribute__((noinline))
+#else
+  #define BW_NOINLINE
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+  #define BW_LIKELY(x) __builtin_expect(!!(x), 1)
+  #define BW_UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+  #define BW_LIKELY(x) x
+  #define BW_UNLIKELY(x) x
+#endif
+
+#define BW_ENSURE_CAPACITY(bw, cap, count_size)     \
+    do {                                            \
+        if (!ensureCapacity((bw), (cap))) {         \
+            if (bw->counting_size) {                \
+                bw->counted_size += count_size;     \
+            }                                       \
+            return;                                 \
+        }                                           \
+    } while (0)
+
+
+static const unsigned char DEC2HEX[16] = {
+    '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
+};
+
+typedef struct BufferWriter {
+    unsigned char* end; // the most commonly-accessed field
+    unsigned char* capacity;
+    unsigned char* data;
+
+    bool counting_size;
+    size_t counted_size;
+
+    // invariants:
+    // if (counting_size), end, capacity, and data are null
+    // else, counted_size is zero and:
+    // if (end <= capacity), then data is non-null and end >= data
+    // if (capacity == 0), then data and end are null, and object is dead
+
+    // Note: We mark the object dead upon growth failure in order
+    // to avoid the need to check for errors after every single
+    // append function.  The error can be raised upon the final
+    // pointer release.
+} BufferWriter;
+
+BW_NOINLINE bool actuallyGrow(BufferWriter* bw, size_t amount) {
+    if (bw->capacity == 0) {
+        // object is dead, ignore append request
+        return false;
+    }
+
+    size_t size = bw->end - bw->data;
+    size_t newCapacity = bw->capacity - bw->data;
+    do {
+        // TODO: detect overflow?
+        newCapacity *= 2;
+    } while (newCapacity < size + amount);
+
+    unsigned char* newData = realloc(bw->data, newCapacity);
+    if (newData) {
+        bw->data = newData;
+        bw->end = newData + size;
+        bw->capacity = newData + newCapacity;
+        return true;
+    } else {
+        // TODO: try smaller growth?
+        free(bw->data);
+        bw->data = 0;
+        bw->end = 0;
+        bw->capacity = 0;
+        return false;
+    }
+}
+
+static inline bool ensureCapacity(BufferWriter* bw, size_t amount) {
+    if (BW_LIKELY(bw->end + amount <= bw->capacity)) {
+        return true;
+    } else {
+        return actuallyGrow(bw, amount);
+    }
+}
+
+BufferWriter* bw_new(size_t initialCapacity) {
+    if (!initialCapacity) {
+        initialCapacity = 1;
+    }
+
+    BufferWriter* bw = malloc(sizeof(BufferWriter));
+    if (!bw) {
+        return 0;
+    }
+
+    bw->data = malloc(initialCapacity);
+    if (!bw->data) {
+        free(bw);
+        return 0;
+    }
+
+    bw->end = bw->data;
+    bw->capacity = bw->data + initialCapacity;
+    bw->counting_size = false;
+    bw->counted_size = 0;
+    return bw;
+}
+
+BufferWriter* bw_new_length_calculator() {
+    BufferWriter* bw = malloc(sizeof(BufferWriter));
+    if (!bw) {
+        return 0;
+    }
+
+    bw->data = 0;
+    bw->end = 0;
+    bw->capacity = 0;
+    bw->counting_size = true;
+    bw->counted_size = 0;
+    return bw;
+}
+
+void bw_free(BufferWriter* bw) {
+    free(bw->data);
+    free(bw);
+}
+
+void bw_trim(BufferWriter* bw) {
+    if (bw->data == 0) {
+        return;
+    }
+
+    if (bw->end < bw->capacity) {
+        // try to shrink
+        size_t size = bw->end - bw->data;
+        unsigned char* data = realloc(bw->data, size);
+        if (data) {
+            bw->data = data;
+            bw->end = data + size;
+            bw->capacity = data + size;
+        } else {
+            // no problem
+        }
+    }
+
+}
+
+size_t bw_get_size(BufferWriter* bw) {
+    if (bw->counting_size) {
+        return bw->counted_size;
+    } else {
+        return bw->end - bw->data;
+    }
+}
+
+unsigned char* bw_release_address(BufferWriter* bw) {
+    unsigned char* data = bw->data;
+
+    bw->end = 0;
+    bw->capacity = 0;
+    bw->data = 0;
+    bw->counting_size = false;
+    bw->counted_size = 0;
+    return data; // null if dead
+}
+
+void bw_append_byte(BufferWriter* bw, unsigned char byte) {
+    BW_ENSURE_CAPACITY(bw, 1, 1);
+
+    bw->end[0] = byte;
+    bw->end += 1;
+}
+
+static inline size_t utf8_char_length(uint32_t c) {
+    if (c <= 0x7F) {
+        return 1;
+    } else if (c <= 0x7FF) {
+        return 2;
+    } else if (c <= 0xFFFF) {
+        return 3;
+    } else if (c <= 0x1FFFFF) {
+        return 4;
+    } else {
+        // Unicode characters out of this range are illegal...
+        return 0;
+    }
+}
+
+void bw_append_char_utf8(BufferWriter* bw, uint32_t c) {
+    BW_ENSURE_CAPACITY(bw, 4, utf8_char_length(c));
+
+    unsigned char* data = bw->end;
+
+    if (c <= 0x7F) {
+        data[0] = c;
+        data += 1;
+    } else if (c <= 0x7FF) {
+        data[0] = 0xC0 | (c >> 6);
+        data[1] = 0x80 | (c & 0x3F);
+        data += 2;
+    } else if (c <= 0xFFFF) {
+        data[0] = 0xE0 | (c >> 12);
+        data[1] = 0x80 | ((c >> 6) & 0x3F);
+        data[2] = 0x80 | (c & 0x3F);
+        data += 3;
+    } else if (c <= 0x1FFFFF) {
+        data[0] = 0xF0 | (c >> 18);
+        data[1] = 0x80 | ((c >> 12) & 0x3F);
+        data[2] = 0x80 | ((c >> 6) & 0x3F);
+        data[3] = 0x80 | (c & 0x3F);
+        data += 4;
+    } else {
+        // Unicode characters out of this range are illegal...
+        // should we assert?  or just ignore...
+    }
+    bw->end = data;
+}
+
+void bw_append_bs(BufferWriter* bw, size_t size, const unsigned char* data) {
+    BW_ENSURE_CAPACITY(bw, size, size);
+
+    memcpy(bw->end, data, size);
+    bw->end += size;
+}
+
+void bw_append_bsz(BufferWriter* bw, const unsigned char* data) {
+    bw_append_bs(bw, strlen((char*)data), data);
+}
+
+void bw_append_byte7(BufferWriter* bw, unsigned char byte) {
+    BW_ENSURE_CAPACITY(bw, 1, 1);
+
+    bw->end[0] = byte & 0x7F;
+    bw->end += 1;
+}
+
+void bw_append_bs7(BufferWriter* bw, size_t size, const unsigned char* data) {
+    BW_ENSURE_CAPACITY(bw, size, size);
+
+    unsigned char* out = bw->end;
+    for (size_t i = 0; i < size; ++i) {
+        out[i] = data[i] & 0x7F;
+    }
+    bw->end += size;
+}
+
+void bw_append_bsz7(BufferWriter* bw, const unsigned char* data) {
+    bw_append_bs7(bw, strlen((char*)data), data);
+}
+
+static inline size_t count_escaped_json_length(size_t size, const unsigned char* data) {
+    size_t rv = 2;
+    for (size_t i = 0; i < size; ++i) {
+        switch (data[i]) {
+            case '\n': rv += 2; break;
+            case '\r': rv += 2; break;
+            case '\t': rv += 2; break;
+            case '\\': rv += 2; break;
+            case '\"': rv += 2; break;
+            default:
+                if (data[i] <= 0x1F) {
+                    rv += 6;
+                } else {
+                    rv += 1;
+                }
+                break;
+        }
+    }
+    return rv;
+}
+
+void bw_append_json_escaped(BufferWriter* bw, size_t size, const unsigned char* data) {
+    BW_ENSURE_CAPACITY(bw, 2 + size * 6, count_escaped_json_length(size, data));
+
+    unsigned char* dest = bw->end;
+    *dest++ = '\"';
+
+    size_t i = 0;
+    while (i < size) {
+        switch (data[i]) {
+            case '\n': *dest++ = '\\'; *dest++ = 'n';  break;
+            case '\r': *dest++ = '\\'; *dest++ = 'r';  break;
+            case '\t': *dest++ = '\\'; *dest++ = 't';  break;
+            case '\\': *dest++ = '\\'; *dest++ = '\\'; break;
+            case '\"': *dest++ = '\\'; *dest++ = '\"'; break;
+            default: {
+                unsigned char d = data[i];
+                if (d <= 0x1F) {
+                    *dest++ = '\\';
+                    *dest++ = 'u';
+                    *dest++ = '0';
+                    *dest++ = '0';
+                    *dest++ = DEC2HEX[d >> 4];
+                    *dest++ = DEC2HEX[d & 0xF];
+                } else {
+                    *dest++ = d;
+                }
+                break;
+            }
+        }
+        ++i;
+    }
+
+    *dest++ = '\"';
+    bw->end = dest;
+}
+
+static inline size_t count_decimal_signed_int_length(int64_t i) {
+    char buf[32]; // enough
+    return i64toa_branchlut(i, buf);
+}
+
+void bw_append_decimal_signed_int(BufferWriter* bw, int64_t i) {
+    BW_ENSURE_CAPACITY(bw, 32, count_decimal_signed_int_length(i)); // enough
+
+    unsigned used = i64toa_branchlut(i, (char*)bw->end);
+    bw->end += used;
+}
+
+static inline size_t count_decimal_double_length(double d) {
+    char buf[318];
+    return sprintf(buf, "%f", d);
+}
+
+void bw_append_decimal_double(BufferWriter* bw, double d) {
+    BW_ENSURE_CAPACITY(bw, 318, count_decimal_double_length(d)); // sprintf("%f", -DBL_MAX);
+
+    size_t used = sprintf((char*)bw->end, "%f", d);
+    bw->end += used;
+}
+
+// From <https://github.com/bos/text/blob/f74427c954fb4479f9db5025f27775e29ace125f/cbits/cbits.c#L234>
+void bw_encode_utf8(uint8_t **destp, const uint16_t *src, size_t srcoff, size_t srclen) {
+  const uint16_t *srcend;
+  uint8_t *dest = *destp;
+
+  src += srcoff;
+  srcend = src + srclen;
+
+ ascii:
+#if defined(__x86_64__)
+  while (srcend - src >= 4) {
+    uint64_t w = *((uint64_t *) src);
+
+    if (w & 0xFF80FF80FF80FF80ULL) {
+      if (!(w & 0x000000000000FF80ULL)) {
+        *dest++ = w & 0xFFFF;
+        src++;
+        if (!(w & 0x00000000FF800000ULL)) {
+          *dest++ = (w >> 16) & 0xFFFF;
+          src++;
+          if (!(w & 0x0000FF8000000000ULL)) {
+            *dest++ = (w >> 32) & 0xFFFF;
+            src++;
+          }
+        }
+      }
+      break;
+    }
+    *dest++ = w & 0xFFFF;
+    *dest++ = (w >> 16) & 0xFFFF;
+    *dest++ = (w >> 32) & 0xFFFF;
+    *dest++ = w >> 48;
+    src += 4;
+  }
+#endif
+
+#if defined(__i386__)
+  while (srcend - src >= 2) {
+    uint32_t w = *((uint32_t *) src);
+
+    if (w & 0xFF80FF80)
+      break;
+    *dest++ = w & 0xFFFF;
+    *dest++ = w >> 16;
+    src += 2;
+  }
+#endif
+
+  while (src < srcend) {
+    uint16_t w = *src++;
+
+    if (w <= 0x7F) {
+      *dest++ = w;
+      /* An ASCII byte is likely to begin a run of ASCII bytes.
+     Falling back into the fast path really helps performance. */
+      goto ascii;
+    }
+    else if (w <= 0x7FF) {
+      *dest++ = (w >> 6) | 0xC0;
+      *dest++ = (w & 0x3f) | 0x80;
+    }
+    else if (w < 0xD800 || w > 0xDBFF) {
+      *dest++ = (w >> 12) | 0xE0;
+      *dest++ = ((w >> 6) & 0x3F) | 0x80;
+      *dest++ = (w & 0x3F) | 0x80;
+    } else {
+      uint32_t c = ((((uint32_t) w) - 0xD800) << 10) +
+    (((uint32_t) *src++) - 0xDC00) + 0x10000;
+      *dest++ = (c >> 18) | 0xF0;
+      *dest++ = ((c >> 12) & 0x3F) | 0x80;
+      *dest++ = ((c >> 6) & 0x3F) | 0x80;
+      *dest++ = (c & 0x3F) | 0x80;
+    }
+  }
+
+  *destp = dest;
+}
+
+void bw_append_json_escaped_utf16(BufferWriter* bw, size_t size, unsigned short* words) {
+    // TODO: handle malloc failure
+    // ideally no temp buffer would be required here.
+    unsigned char* utf8 = malloc(size * 4);
+    assert(utf8);
+    unsigned char* utf8end = utf8;
+
+    bw_encode_utf8(&utf8end, words, 0, size);
+
+    bw_append_json_escaped(bw, utf8end - utf8, utf8);
+
+    free(utf8);
+}
+
+static const unsigned char UNRESERVED[256] = {
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,1,1,0,
+    1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
+
+    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
+    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,1,
+    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
+    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,1,0,
+
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
+    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
+};
+
+static inline size_t count_url_encoded_length(size_t size, const unsigned char* data) {
+    size_t rv = 0;
+    for (size_t i = 0; i < size; ++i) {
+        if (UNRESERVED[data[i]]) {
+            rv += 1;
+        } else {
+            rv += 3;
+        }
+    }
+    return rv;
+}
+
+void bw_append_url_encoded(BufferWriter* bw, size_t size, const unsigned char* data) {
+    // worst case
+    BW_ENSURE_CAPACITY(bw, size * 3, count_url_encoded_length(size, data));
+
+    const unsigned char* src = data;
+    unsigned char* dst = bw->end;
+    while (size--) {
+        unsigned char c = *src++;
+        if (UNRESERVED[c]) {
+            *dst++ = c;
+        } else {
+            dst[0] = '%';
+            dst[1] = DEC2HEX[c >> 4];
+            dst[2] = DEC2HEX[c & 0xF];
+            dst += 3;
+        }
+    }
+
+    bw->end = dst;
+}
diff --git a/cbits/buffer.cpp b/cbits/buffer.cpp
deleted file mode 100644
--- a/cbits/buffer.cpp
+++ /dev/null
@@ -1,492 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-#include <assert.h>
-
-#include "branchlut.h"
-
-/*
- * Append utf8, ASCII7, raw bytes, utf16
- * JSON encode utf8, ascii7, utf16
- */
-
-#if defined(__clang__) || defined(__GNUC__)
-  #define BW_NOINLINE __attribute__((noinline))
-#else
-  #define BW_NOINLINE
-#endif
-
-#if defined(__clang__) || defined(__GNUC__)
-  #define BW_LIKELY(x) __builtin_expect(!!(x), 1)
-  #define BW_UNLIKELY(x) __builtin_expect(!!(x), 0)
-#else
-  #define BW_LIKELY(x) x
-  #define BW_UNLIKELY(x) x
-#endif
-
-#define BW_ENSURE_CAPACITY(bw, cap, count_size)     \
-    do {                                            \
-        if (!ensureCapacity((bw), (cap))) {         \
-            if (bw->counting_size) {                \
-                bw->counted_size += count_size;     \
-            }                                       \
-            return;                                 \
-        }                                           \
-    } while (0)
-
-
-namespace {
-    static const unsigned char DEC2HEX[16] = {
-        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
-    };
-
-    struct BufferWriter {
-        unsigned char* end; // the most commonly-accessed field
-        unsigned char* capacity;
-        unsigned char* data;
-
-        bool counting_size;
-        size_t counted_size;
-
-        // invariants:
-        // if (counting_size), end, capacity, and data are null
-        // else, counted_size is zero and:
-        // if (end <= capacity), then data is non-null and end >= data
-        // if (capacity == 0), then data and end are null, and object is dead
-
-        // Note: We mark the object dead upon growth failure in order
-        // to avoid the need to check for errors after every single
-        // append function.  The error can be raised upon the final
-        // pointer release.
-    };
-
-    BW_NOINLINE bool actuallyGrow(BufferWriter* bw, size_t amount) {
-        if (bw->capacity == 0) {
-            // object is dead, ignore append request
-            return false;
-        }
-
-        size_t size = bw->end - bw->data;
-        size_t newCapacity = bw->capacity - bw->data;
-        do {
-            // TODO: detect overflow?
-            newCapacity *= 2;
-        } while (newCapacity < size + amount);
-
-        unsigned char* newData = reinterpret_cast<unsigned char*>(realloc(bw->data, newCapacity));
-        if (newData) {
-            bw->data = newData;
-            bw->end = newData + size;
-            bw->capacity = newData + newCapacity;
-            return true;
-        } else {
-            // TODO: try smaller growth?
-            free(bw->data);
-            bw->data = 0;
-            bw->end = 0;
-            bw->capacity = 0;
-            return false;
-        }
-    }
-
-    inline bool ensureCapacity(BufferWriter* bw, size_t amount) {
-        if (BW_LIKELY(bw->end + amount <= bw->capacity)) {
-            return true;
-        } else {
-            return actuallyGrow(bw, amount);
-        }
-    }
-}
-
-extern "C" BufferWriter* bw_new(size_t initialCapacity) {
-    if (!initialCapacity) {
-        initialCapacity = 1;
-    }
-
-    BufferWriter* bw = reinterpret_cast<BufferWriter*>(malloc(sizeof(BufferWriter)));
-    if (!bw) {
-        return 0;
-    }
-
-    bw->data = reinterpret_cast<unsigned char*>(malloc(initialCapacity));
-    if (!bw->data) {
-        free(bw);
-        return 0;
-    }
-
-    bw->end = bw->data;
-    bw->capacity = bw->data + initialCapacity;
-    bw->counting_size = false;
-    bw->counted_size = 0;
-    return bw;
-}
-
-extern "C" BufferWriter* bw_new_length_calculator() {
-    BufferWriter* bw = reinterpret_cast<BufferWriter*>(malloc(sizeof(BufferWriter)));
-    if (!bw) {
-        return 0;
-    }
-
-    bw->data = 0;
-    bw->end = 0;
-    bw->capacity = 0;
-    bw->counting_size = true;
-    bw->counted_size = 0;
-    return bw;
-}
-
-extern "C" void bw_free(BufferWriter* bw) {
-    free(bw->data);
-    free(bw);
-}
-
-extern "C" void bw_trim(BufferWriter* bw) {
-    if (bw->data == 0) {
-        return;
-    }
-
-    if (bw->end < bw->capacity) {
-        // try to shrink
-        size_t size = bw->end - bw->data;
-        unsigned char* data = reinterpret_cast<unsigned char*>(realloc(bw->data, size));
-        if (data) {
-            bw->data = data;
-            bw->end = data + size;
-            bw->capacity = data + size;
-        } else {
-            // no problem
-        }
-    }
-
-}
-
-extern "C" size_t bw_get_size(BufferWriter* bw) {
-    if (bw->counting_size) {
-        return bw->counted_size;
-    } else {
-        return bw->end - bw->data;
-    }
-}
-
-extern "C" unsigned char* bw_release_address(BufferWriter* bw) {
-    unsigned char* data = bw->data;
-
-    bw->end = 0;
-    bw->capacity = 0;
-    bw->data = 0;
-    bw->counting_size = false;
-    bw->counted_size = 0;
-    return data; // null if dead
-}
-
-extern "C" void bw_append_byte(BufferWriter* bw, unsigned char byte) {
-    BW_ENSURE_CAPACITY(bw, 1, 1);
-
-    bw->end[0] = byte;
-    bw->end += 1;
-}
-
-inline size_t utf8_char_length(uint32_t c) {
-    if (c <= 0x7F) {
-        return 1;
-    } else if (c <= 0x7FF) {
-        return 2;
-    } else if (c <= 0xFFFF) {
-        return 3;
-    } else if (c <= 0x1FFFFF) {
-        return 4;
-    } else {
-        // Unicode characters out of this range are illegal...
-        return 0;
-    }
-}
-
-extern "C" void bw_append_char_utf8(BufferWriter* bw, uint32_t c) {
-    BW_ENSURE_CAPACITY(bw, 4, utf8_char_length(c));
-
-    unsigned char* data = bw->end;
-
-    if (c <= 0x7F) {
-        data[0] = static_cast<unsigned char>(c);
-        data += 1;
-    } else if (c <= 0x7FF) {
-        data[0] = static_cast<unsigned char>(0xC0 | (c >> 6));
-        data[1] = static_cast<unsigned char>(0x80 | (c & 0x3F));
-        data += 2;
-    } else if (c <= 0xFFFF) {
-        data[0] = static_cast<unsigned char>(0xE0 | (c >> 12));
-        data[1] = static_cast<unsigned char>(0x80 | ((c >> 6) & 0x3F));
-        data[2] = static_cast<unsigned char>(0x80 | (c & 0x3F));
-        data += 3;
-    } else if (c <= 0x1FFFFF) {
-        data[0] = static_cast<unsigned char>(0xF0 | (c >> 18));
-        data[1] = static_cast<unsigned char>(0x80 | ((c >> 12) & 0x3F));
-        data[2] = static_cast<unsigned char>(0x80 | ((c >> 6) & 0x3F));
-        data[3] = static_cast<unsigned char>(0x80 | (c & 0x3F));
-        data += 4;
-    } else {
-        // Unicode characters out of this range are illegal...
-        // should we assert?  or just ignore...
-    }
-    bw->end = data;
-}
-
-extern "C" void bw_append_bs(BufferWriter* bw, size_t size, const unsigned char* data) {
-    BW_ENSURE_CAPACITY(bw, size, size);
-
-    memcpy(bw->end, data, size);
-    bw->end += size;
-}
-
-extern "C" void bw_append_bsz(BufferWriter* bw, const unsigned char* data) {
-    bw_append_bs(bw, strlen(reinterpret_cast<const char*>(data)), data);
-}
-
-extern "C" void bw_append_byte7(BufferWriter* bw, unsigned char byte) {
-    BW_ENSURE_CAPACITY(bw, 1, 1);
-
-    bw->end[0] = byte & 0x7F;
-    bw->end += 1;
-}
-
-extern "C" void bw_append_bs7(BufferWriter* bw, size_t size, const unsigned char* data) {
-    BW_ENSURE_CAPACITY(bw, size, size);
-
-    unsigned char* out = bw->end;
-    for (size_t i = 0; i < size; ++i) {
-        out[i] = data[i] & 0x7F;
-    }
-    bw->end += size;
-}
-
-extern "C" void bw_append_bsz7(BufferWriter* bw, const unsigned char* data) {
-    bw_append_bs7(bw, strlen(reinterpret_cast<const char*>(data)), data);
-}
-
-inline size_t count_escaped_json_length(size_t size, const unsigned char* data) {
-    size_t rv = 2;
-    for (size_t i = 0; i < size; ++i) {
-        switch (data[i]) {
-            case '\n': rv += 2; break;
-            case '\r': rv += 2; break;
-            case '\t': rv += 2; break;
-            case '\\': rv += 2; break;
-            case '\"': rv += 2; break;
-            default:
-                if (data[i] <= 0x1F) {
-                    rv += 6;
-                } else {
-                    rv += 1;
-                }
-                break;
-        }
-    }
-    return rv;
-}
-
-extern "C" void bw_append_json_escaped(BufferWriter* bw, size_t size, const unsigned char* data) {
-    BW_ENSURE_CAPACITY(bw, 2 + size * 6, count_escaped_json_length(size, data));
-
-    unsigned char* dest = bw->end;
-    *dest++ = '\"';
-
-    size_t i = 0;
-    while (i < size) {
-        switch (data[i]) {
-            case '\n': *dest++ = '\\'; *dest++ = 'n';  break;
-            case '\r': *dest++ = '\\'; *dest++ = 'r';  break;
-            case '\t': *dest++ = '\\'; *dest++ = 't';  break;
-            case '\\': *dest++ = '\\'; *dest++ = '\\'; break;
-            case '\"': *dest++ = '\\'; *dest++ = '\"'; break;
-            default:
-                unsigned char d = data[i];
-                if (d <= 0x1F) {
-                    *dest++ = '\\';
-                    *dest++ = 'u';
-                    *dest++ = '0';
-                    *dest++ = '0';
-                    *dest++ = DEC2HEX[d >> 4];
-                    *dest++ = DEC2HEX[d & 0xF];
-                } else {
-                    *dest++ = d;
-                }
-                break;
-        }
-        ++i;
-    }
-
-    *dest++ = '\"';
-    bw->end = dest;
-}
-
-inline size_t count_decimal_signed_int_length(int64_t i) {
-    char buf[32]; // enough
-    return i64toa_branchlut(i, buf);
-}
-
-extern "C" void bw_append_decimal_signed_int(BufferWriter* bw, int64_t i) {
-    BW_ENSURE_CAPACITY(bw, 32, count_decimal_signed_int_length(i)); // enough
-
-    unsigned used = i64toa_branchlut(i, reinterpret_cast<char*>(bw->end));
-    bw->end += used;
-}
-
-inline size_t count_decimal_double_length(double d) {
-    char buf[318];
-    return sprintf(buf, "%f", d);
-}
-
-extern "C" void bw_append_decimal_double(BufferWriter* bw, double d) {
-    BW_ENSURE_CAPACITY(bw, 318, count_decimal_double_length(d)); // sprintf("%f", -DBL_MAX);
-
-    size_t used = sprintf(reinterpret_cast<char*>(bw->end), "%f", d);
-    bw->end += used;
-}
-
-// From <https://github.com/bos/text/blob/f74427c954fb4479f9db5025f27775e29ace125f/cbits/cbits.c#L234>
-extern "C" void bw_encode_utf8(uint8_t **destp, const uint16_t *src, size_t srcoff, size_t srclen) {
-  const uint16_t *srcend;
-  uint8_t *dest = *destp;
-
-  src += srcoff;
-  srcend = src + srclen;
-
- ascii:
-#if defined(__x86_64__)
-  while (srcend - src >= 4) {
-    uint64_t w = *((uint64_t *) src);
-
-    if (w & 0xFF80FF80FF80FF80ULL) {
-      if (!(w & 0x000000000000FF80ULL)) {
-        *dest++ = w & 0xFFFF;
-        src++;
-        if (!(w & 0x00000000FF800000ULL)) {
-          *dest++ = (w >> 16) & 0xFFFF;
-          src++;
-          if (!(w & 0x0000FF8000000000ULL)) {
-            *dest++ = (w >> 32) & 0xFFFF;
-            src++;
-          }
-        }
-      }
-      break;
-    }
-    *dest++ = w & 0xFFFF;
-    *dest++ = (w >> 16) & 0xFFFF;
-    *dest++ = (w >> 32) & 0xFFFF;
-    *dest++ = w >> 48;
-    src += 4;
-  }
-#endif
-
-#if defined(__i386__)
-  while (srcend - src >= 2) {
-    uint32_t w = *((uint32_t *) src);
-
-    if (w & 0xFF80FF80)
-      break;
-    *dest++ = w & 0xFFFF;
-    *dest++ = w >> 16;
-    src += 2;
-  }
-#endif
-
-  while (src < srcend) {
-    uint16_t w = *src++;
-
-    if (w <= 0x7F) {
-      *dest++ = w;
-      /* An ASCII byte is likely to begin a run of ASCII bytes.
-     Falling back into the fast path really helps performance. */
-      goto ascii;
-    }
-    else if (w <= 0x7FF) {
-      *dest++ = (w >> 6) | 0xC0;
-      *dest++ = (w & 0x3f) | 0x80;
-    }
-    else if (w < 0xD800 || w > 0xDBFF) {
-      *dest++ = (w >> 12) | 0xE0;
-      *dest++ = ((w >> 6) & 0x3F) | 0x80;
-      *dest++ = (w & 0x3F) | 0x80;
-    } else {
-      uint32_t c = ((((uint32_t) w) - 0xD800) << 10) +
-    (((uint32_t) *src++) - 0xDC00) + 0x10000;
-      *dest++ = (c >> 18) | 0xF0;
-      *dest++ = ((c >> 12) & 0x3F) | 0x80;
-      *dest++ = ((c >> 6) & 0x3F) | 0x80;
-      *dest++ = (c & 0x3F) | 0x80;
-    }
-  }
-
-  *destp = dest;
-}
-
-extern "C" void bw_append_json_escaped_utf16(BufferWriter* bw, size_t size, unsigned short* words) {
-    // TODO: handle malloc failure
-    // ideally no temp buffer would be required here.
-    unsigned char* utf8 = reinterpret_cast<unsigned char*>(malloc(size * 4));
-    assert(utf8);
-    unsigned char* utf8end = utf8;
-
-    bw_encode_utf8(&utf8end, words, 0, size);
-
-    bw_append_json_escaped(bw, utf8end - utf8, utf8);
-
-    free(utf8);
-}
-
-static const unsigned char UNRESERVED[256] = {
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,1,1,0,
-    1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
-
-    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
-    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,1,
-    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
-    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,1,0,
-
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
-    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
-};
-
-inline size_t count_url_encoded_length(size_t size, const unsigned char* data) {
-    size_t rv = 0;
-    for (size_t i = 0; i < size; ++i) {
-        if (UNRESERVED[data[i]]) {
-            rv += 1;
-        } else {
-            rv += 3;
-        }
-    }
-    return rv;
-}
-
-extern "C" void bw_append_url_encoded(BufferWriter* bw, size_t size, const unsigned char* data) {
-    // worst case
-    BW_ENSURE_CAPACITY(bw, size * 3, count_url_encoded_length(size, data));
-
-    const unsigned char* src = data;
-    unsigned char* dst = bw->end;
-    while (size--) {
-        unsigned char c = *src++;
-        if (UNRESERVED[c]) {
-            *dst++ = c;
-        } else {
-            dst[0] = '%';
-            dst[1] = DEC2HEX[c >> 4];
-            dst[2] = DEC2HEX[c & 0xF];
-            dst += 3;
-        }
-    }
-
-    bw->end = dst;
-}
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,7 @@
+0.2.4.7
+
+* Remove any dependencies on C++
+
 0.2.4.6
 
 * Fix package compilation on GHC 7.8 and 7.10.  (Thanks Xia Li-yao!)
