You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucy.apache.org by nw...@apache.org on 2013/09/01 22:17:07 UTC

[lucy-commits] [19/24] git commit: refs/heads/cfish-string-prep1 - Initial implementation of CharBuf

Initial implementation of CharBuf

Mostly copied from String.


Project: http://git-wip-us.apache.org/repos/asf/lucy/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy/commit/0806c59c
Tree: http://git-wip-us.apache.org/repos/asf/lucy/tree/0806c59c
Diff: http://git-wip-us.apache.org/repos/asf/lucy/diff/0806c59c

Branch: refs/heads/cfish-string-prep1
Commit: 0806c59c453f49b7d4232cf48f87e5fe77453ba3
Parents: 71b1166
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Sun Sep 1 18:01:20 2013 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Sun Sep 1 22:05:02 2013 +0200

----------------------------------------------------------------------
 clownfish/runtime/core/Clownfish/CharBuf.c      | 414 +++++++++++++++++++
 clownfish/runtime/core/Clownfish/CharBuf.cfh    | 152 +++++++
 clownfish/runtime/core/Clownfish/Test.c         |   2 +
 .../runtime/core/Clownfish/Test/TestCharBuf.c   | 300 ++++++++++++++
 .../runtime/core/Clownfish/Test/TestCharBuf.cfh |  29 ++
 5 files changed, 897 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy/blob/0806c59c/clownfish/runtime/core/Clownfish/CharBuf.c
----------------------------------------------------------------------
diff --git a/clownfish/runtime/core/Clownfish/CharBuf.c b/clownfish/runtime/core/Clownfish/CharBuf.c
new file mode 100644
index 0000000..8b6de34
--- /dev/null
+++ b/clownfish/runtime/core/Clownfish/CharBuf.c
@@ -0,0 +1,414 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define C_CFISH_CHARBUF
+#define C_CFISH_STRING
+#define CFISH_USE_SHORT_NAMES
+#define CHY_USE_SHORT_NAMES
+
+#include "charmony.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "Clownfish/CharBuf.h"
+
+#include "Clownfish/Err.h"
+#include "Clownfish/String.h"
+#include "Clownfish/Util/Memory.h"
+#include "Clownfish/Util/StringHelper.h"
+#include "Clownfish/VTable.h"
+
+// Helper function for throwing invalid UTF-8 error. Since THROW uses
+// a String internally, calling THROW with invalid UTF-8 would create an
+// infinite loop -- so we fwrite some of the bogus text to stderr and
+// invoke THROW with a generic message.
+#define DIE_INVALID_UTF8(text, size) \
+    S_die_invalid_utf8(text, size, __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)
+static void
+S_die_invalid_utf8(const char *text, size_t size, const char *file, int line,
+                   const char *func);
+
+// Helper function for throwing invalid pattern error.
+static void
+S_die_invalid_pattern(const char *pattern);
+
+CharBuf*
+CB_new(size_t size) {
+    CharBuf *self = (CharBuf*)VTable_Make_Obj(CHARBUF);
+    return CB_init(self, size);
+}
+
+CharBuf*
+CB_init(CharBuf *self, size_t size) {
+    // Derive.
+    self->ptr = (char*)MALLOCATE(size + 1);
+
+    // Init.
+    *self->ptr = '\0'; // Empty string.
+
+    // Assign.
+    self->size = 0;
+    self->cap  = size + 1;
+
+    return self;
+}
+
+CharBuf*
+CB_new_from_str(String *string) {
+    return CB_new_from_trusted_utf8(string->ptr, string->size);
+}
+
+CharBuf*
+CB_new_from_utf8(const char *ptr, size_t size) {
+    if (!StrHelp_utf8_valid(ptr, size)) {
+        DIE_INVALID_UTF8(ptr, size);
+    }
+    return CB_new_from_trusted_utf8(ptr, size);
+}
+
+CharBuf*
+CB_new_from_trusted_utf8(const char *ptr, size_t size) {
+    CharBuf *self = (CharBuf*)VTable_Make_Obj(CHARBUF);
+
+    // Derive.
+    self->ptr = (char*)MALLOCATE(size + 1);
+
+    // Copy.
+    memcpy(self->ptr, ptr, size);
+
+    // Assign.
+    self->size      = size;
+    self->cap       = size + 1;
+    self->ptr[size] = '\0'; // Null terminate.
+
+    return self;
+}
+
+CharBuf*
+CB_newf(const char *pattern, ...) {
+    CharBuf *self = CB_new(strlen(pattern));
+    va_list args;
+    va_start(args, pattern);
+    CB_VCatF(self, pattern, args);
+    va_end(args);
+    return self;
+}
+
+void
+CB_Destroy_IMP(CharBuf *self) {
+    FREEMEM(self->ptr);
+    SUPER_DESTROY(self, CHARBUF);
+}
+
+static void
+S_grow(CharBuf *self, size_t size) {
+    if (size >= self->cap) {
+        CB_Grow(self, size);
+    }
+}
+
+char*
+CB_Grow_IMP(CharBuf *self, size_t size) {
+    if (size >= self->cap) {
+        self->cap = size + 1;
+        self->ptr = (char*)REALLOCATE(self->ptr, self->cap);
+    }
+    return self->ptr;
+}
+
+static void
+S_die_invalid_utf8(const char *text, size_t size, const char *file, int line,
+                   const char *func) {
+    fprintf(stderr, "Invalid UTF-8, aborting: '");
+    fwrite(text, sizeof(char), size < 200 ? size : 200, stderr);
+    if (size > 200) { fwrite("[...]", sizeof(char), 5, stderr); }
+    fprintf(stderr, "' (length %lu)\n", (unsigned long)size);
+    Err_throw_at(ERR, file, line, func, "Invalid UTF-8");
+}
+
+static void
+S_die_invalid_pattern(const char *pattern) {
+    size_t  pattern_len = strlen(pattern);
+    fprintf(stderr, "Invalid pattern, aborting: '");
+    fwrite(pattern, sizeof(char), pattern_len, stderr);
+    fprintf(stderr, "'\n");
+    THROW(ERR, "Invalid pattern.");
+}
+
+void
+CB_catf(CharBuf *self, const char *pattern, ...) {
+    va_list args;
+    va_start(args, pattern);
+    CB_VCatF(self, pattern, args);
+    va_end(args);
+}
+
+void
+CB_VCatF_IMP(CharBuf *self, const char *pattern, va_list args) {
+    size_t      pattern_len   = strlen(pattern);
+    const char *pattern_start = pattern;
+    const char *pattern_end   = pattern + pattern_len;
+    char        buf[64];
+
+    for (; pattern < pattern_end; pattern++) {
+        const char *slice_end = pattern;
+
+        // Consume all characters leading up to a '%'.
+        while (slice_end < pattern_end && *slice_end != '%') { slice_end++; }
+        if (pattern != slice_end) {
+            size_t size = slice_end - pattern;
+            CB_Cat_Trusted_UTF8(self, pattern, size);
+            pattern = slice_end;
+        }
+
+        if (pattern < pattern_end) {
+            pattern++; // Move past '%'.
+
+            switch (*pattern) {
+                case '%': {
+                        CB_Cat_Trusted_UTF8(self, "%", 1);
+                    }
+                    break;
+                case 'o': {
+                        Obj *obj = va_arg(args, Obj*);
+                        if (!obj) {
+                            CB_Cat_Trusted_UTF8(self, "[NULL]", 6);
+                        }
+                        else if (Obj_Is_A(obj, STRING)) {
+                            CB_Cat(self, (String*)obj);
+                        }
+                        else {
+                            String *string = Obj_To_String(obj);
+                            CB_Cat(self, string);
+                            DECREF(string);
+                        }
+                    }
+                    break;
+                case 'i': {
+                        int64_t val = 0;
+                        size_t size;
+                        if (pattern[1] == '8') {
+                            val = va_arg(args, int32_t);
+                            pattern++;
+                        }
+                        else if (pattern[1] == '3' && pattern[2] == '2') {
+                            val = va_arg(args, int32_t);
+                            pattern += 2;
+                        }
+                        else if (pattern[1] == '6' && pattern[2] == '4') {
+                            val = va_arg(args, int64_t);
+                            pattern += 2;
+                        }
+                        else {
+                            S_die_invalid_pattern(pattern_start);
+                        }
+                        size = sprintf(buf, "%" PRId64, val);
+                        CB_Cat_Trusted_UTF8(self, buf, size);
+                    }
+                    break;
+                case 'u': {
+                        uint64_t val = 0;
+                        size_t size;
+                        if (pattern[1] == '8') {
+                            val = va_arg(args, uint32_t);
+                            pattern += 1;
+                        }
+                        else if (pattern[1] == '3' && pattern[2] == '2') {
+                            val = va_arg(args, uint32_t);
+                            pattern += 2;
+                        }
+                        else if (pattern[1] == '6' && pattern[2] == '4') {
+                            val = va_arg(args, uint64_t);
+                            pattern += 2;
+                        }
+                        else {
+                            S_die_invalid_pattern(pattern_start);
+                        }
+                        size = sprintf(buf, "%" PRIu64, val);
+                        CB_Cat_Trusted_UTF8(self, buf, size);
+                    }
+                    break;
+                case 'f': {
+                        if (pattern[1] == '6' && pattern[2] == '4') {
+                            double num  = va_arg(args, double);
+                            char bigbuf[512];
+                            size_t size = sprintf(bigbuf, "%g", num);
+                            CB_Cat_Trusted_UTF8(self, bigbuf, size);
+                            pattern += 2;
+                        }
+                        else {
+                            S_die_invalid_pattern(pattern_start);
+                        }
+                    }
+                    break;
+                case 'x': {
+                        if (pattern[1] == '3' && pattern[2] == '2') {
+                            unsigned long val = va_arg(args, uint32_t);
+                            size_t size = sprintf(buf, "%.8lx", val);
+                            CB_Cat_Trusted_UTF8(self, buf, size);
+                            pattern += 2;
+                        }
+                        else {
+                            S_die_invalid_pattern(pattern_start);
+                        }
+                    }
+                    break;
+                case 's': {
+                        char *string = va_arg(args, char*);
+                        if (string == NULL) {
+                            CB_Cat_Trusted_UTF8(self, "[NULL]", 6);
+                        }
+                        else {
+                            size_t size = strlen(string);
+                            if (StrHelp_utf8_valid(string, size)) {
+                                CB_Cat_Trusted_UTF8(self, string, size);
+                            }
+                            else {
+                                CB_Cat_Trusted_UTF8(self, "[INVALID UTF8]", 14);
+                            }
+                        }
+                    }
+                    break;
+                default: {
+                        // Assume NULL-terminated pattern string, which
+                        // eliminates the need for bounds checking if '%' is
+                        // the last visible character.
+                        S_die_invalid_pattern(pattern_start);
+                    }
+            }
+        }
+    }
+}
+
+String*
+CB_To_String_IMP(CharBuf *self) {
+    return Str_new_from_trusted_utf8(self->ptr, self->size);
+}
+
+String*
+CB_Yield_String_IMP(CharBuf *self) {
+    String *retval
+        = Str_new_steal_from_trusted_str(self->ptr, self->size, self->cap);
+    self->ptr  = NULL;
+    self->size = 0;
+    self->cap  = 0;
+    return retval;
+}
+
+void
+CB_Cat_Char_IMP(CharBuf *self, uint32_t code_point) {
+    const size_t MAX_UTF8_BYTES = 4;
+    if (self->size + MAX_UTF8_BYTES >= self->cap) {
+        S_grow(self, Memory_oversize(self->size + MAX_UTF8_BYTES,
+                                     sizeof(char)));
+    }
+    char *end = self->ptr + self->size;
+    size_t count = StrHelp_encode_utf8_char(code_point, (uint8_t*)end);
+    self->size += count;
+    *(end + count) = '\0';
+}
+
+CharBuf*
+CB_Clone_IMP(CharBuf *self) {
+    return CB_new_from_trusted_utf8(self->ptr, self->size);
+}
+
+void
+CB_Mimic_UTF8_IMP(CharBuf *self, const char* ptr, size_t size) {
+    if (!StrHelp_utf8_valid(ptr, size)) {
+        DIE_INVALID_UTF8(ptr, size);
+    }
+    if (size >= self->cap) { S_grow(self, size); }
+    memmove(self->ptr, ptr, size);
+    self->size = size;
+    self->ptr[size] = '\0';
+}
+
+void
+CB_Mimic_IMP(CharBuf *self, Obj *other) {
+    if (Obj_Is_A(other, CHARBUF)) {
+        CharBuf *twin = (CharBuf*)other;
+        if (twin->size >= self->cap) { S_grow(self, twin->size); }
+        memmove(self->ptr, twin->ptr, twin->size);
+        self->size = twin->size;
+        self->ptr[twin->size] = '\0';
+    }
+    else if (Obj_Is_A(other, STRING)) {
+        String *twin = (String*)other;
+        if (twin->size >= self->cap) { S_grow(self, twin->size); }
+        memmove(self->ptr, twin->ptr, twin->size);
+        self->size = twin->size;
+        self->ptr[twin->size] = '\0';
+    }
+    else {
+        THROW(ERR, "CharBuf can't mimic %o", Obj_Get_Class_Name(other));
+    }
+}
+
+void
+CB_Cat_UTF8_IMP(CharBuf *self, const char* ptr, size_t size) {
+    if (!StrHelp_utf8_valid(ptr, size)) {
+        DIE_INVALID_UTF8(ptr, size);
+    }
+    CB_Cat_Trusted_UTF8_IMP(self, ptr, size);
+}
+
+void
+CB_Cat_Trusted_UTF8_IMP(CharBuf *self, const char* ptr, size_t size) {
+    const size_t new_size = self->size + size;
+    if (new_size >= self->cap) {
+        size_t amount = Memory_oversize(new_size, sizeof(char));
+        S_grow(self, amount);
+    }
+    memcpy((self->ptr + self->size), ptr, size);
+    self->size = new_size;
+    self->ptr[new_size] = '\0';
+}
+
+void
+CB_Cat_IMP(CharBuf *self, const String *string) {
+    const size_t new_size = self->size + string->size;
+    if (new_size >= self->cap) {
+        size_t amount = Memory_oversize(new_size, sizeof(char));
+        S_grow(self, amount);
+    }
+    memcpy((self->ptr + self->size), string->ptr, string->size);
+    self->size = new_size;
+    self->ptr[new_size] = '\0';
+}
+
+void
+CB_Set_Size_IMP(CharBuf *self, size_t size) {
+    if (size >= self->cap) {
+        THROW(ERR, "Can't set size of CharBuf beyond capacity");
+    }
+    self->size = size;
+}
+
+size_t
+CB_Get_Size_IMP(CharBuf *self) {
+    return self->size;
+}
+
+uint8_t*
+CB_Get_Ptr8_IMP(CharBuf *self) {
+    return (uint8_t*)self->ptr;
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/0806c59c/clownfish/runtime/core/Clownfish/CharBuf.cfh
----------------------------------------------------------------------
diff --git a/clownfish/runtime/core/Clownfish/CharBuf.cfh b/clownfish/runtime/core/Clownfish/CharBuf.cfh
new file mode 100644
index 0000000..105386c
--- /dev/null
+++ b/clownfish/runtime/core/Clownfish/CharBuf.cfh
@@ -0,0 +1,152 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+parcel Clownfish;
+
+/**
+ * Growable buffer holding Unicode characters.
+ */
+
+class Clownfish::CharBuf cnick CB
+    inherits Clownfish::Obj {
+
+    char    *ptr;
+    size_t   size;
+    size_t   cap;  /* allocated bytes, including terminating null */
+
+    inert incremented CharBuf*
+    new(size_t size);
+
+    inert CharBuf*
+    init(CharBuf *self, size_t size);
+
+    /** Return a new CharBuf which holds a copy of the passed-in String.
+     */
+    inert incremented CharBuf*
+    new_from_str(String *string);
+
+    /** Return a new CharBuf which holds a copy of the passed-in string.
+     * Check for UTF-8 validity.
+     */
+    inert incremented CharBuf*
+    new_from_utf8(const char *utf8, size_t size);
+
+    /** Return a new CharBuf which holds a copy of the passed-in string.  No
+     * validity checking is performed.
+     */
+    inert incremented CharBuf*
+    new_from_trusted_utf8(const char *utf8, size_t size);
+
+    /** Return a pointer to a new CharBuf which contains formatted data
+     * expanded according to Str_VCatF.
+     *
+     * Note: a user-supplied <code>pattern</code> string is a security hole
+     * and must not be allowed.
+     */
+    inert incremented CharBuf*
+    newf(const char *pattern, ...);
+
+    public void
+    Mimic(CharBuf *self, Obj *other);
+
+    void
+    Mimic_UTF8(CharBuf *self, const char *ptr, size_t size);
+
+    /** Concatenate the passed-in string onto the end of the CharBuf.
+     */
+    void
+    Cat_UTF8(CharBuf *self, const char *ptr, size_t size);
+
+    /** Concatenate the supplied text onto the end of the CharBuf.  Don't
+     * check for UTF-8 validity.
+     */
+    void
+    Cat_Trusted_UTF8(CharBuf *self, const char *ptr, size_t size);
+
+    /** Concatenate the contents of <code>string</code> onto the end of the
+     * caller.
+     */
+    void
+    Cat(CharBuf *self, const String *string);
+
+    /** Concatenate formatted arguments.  Similar to the printf family, but
+     * only accepts minimal options (just enough for decent error messages).
+     *
+     * Objects:  %o
+     * char*:    %s
+     * integers: %i8 %i32 %i64 %u8 %u32 %u64
+     * floats:   %f64
+     * hex:      %x32
+     *
+     * Note that all Clownfish Objects, including CharBufs, are printed via
+     * %o (which invokes Obj_To_CharBuf()).
+     */
+    void
+    VCatF(CharBuf *self, const char *pattern, va_list args);
+
+    /** Invokes Str_VCatF to concatenate formatted arguments.  Note that this
+     * is only a function and not a method.
+     */
+    inert void
+    catf(CharBuf *self, const char *pattern, ...);
+
+    /** Concatenate one Unicode character onto the end of the CharBuf.
+     */
+    void
+    Cat_Char(CharBuf *self, uint32_t code_point);
+
+    /** Assign more memory to the CharBuf, if it doesn't already have enough
+     * room to hold a string of <code>size</code> bytes.  Cannot shrink the
+     * allocation.
+     *
+     * @return a pointer to the raw buffer.
+     */
+    char*
+    Grow(CharBuf *self, size_t size);
+
+    /** Set the String's <code>size</code> attribute.
+     */
+    void
+    Set_Size(CharBuf *self, size_t size);
+
+    /** Get the CharBuf's <code>size</code> attribute.
+     */
+    size_t
+    Get_Size(CharBuf *self);
+
+    /** Return the internal backing array for the CharBuf if its internal
+     * encoding is UTF-8.  If it is not encoded as UTF-8 throw an exception.
+     */
+    uint8_t*
+    Get_Ptr8(CharBuf *self);
+
+    public incremented CharBuf*
+    Clone(CharBuf *self);
+
+    public void
+    Destroy(CharBuf *self);
+
+    public incremented String*
+    To_String(CharBuf *self);
+
+    /** Return the content of the CharBuf as String and clear the CharBuf.
+     * This is more efficient than To_String().
+     */
+    public incremented String*
+    Yield_String(CharBuf *self);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/0806c59c/clownfish/runtime/core/Clownfish/Test.c
----------------------------------------------------------------------
diff --git a/clownfish/runtime/core/Clownfish/Test.c b/clownfish/runtime/core/Clownfish/Test.c
index 23137e1..b891331 100644
--- a/clownfish/runtime/core/Clownfish/Test.c
+++ b/clownfish/runtime/core/Clownfish/Test.c
@@ -24,6 +24,7 @@
 
 #include "Clownfish/Test/TestByteBuf.h"
 #include "Clownfish/Test/TestString.h"
+#include "Clownfish/Test/TestCharBuf.h"
 #include "Clownfish/Test/TestErr.h"
 #include "Clownfish/Test/TestHash.h"
 #include "Clownfish/Test/TestLockFreeRegistry.h"
@@ -45,6 +46,7 @@ Test_create_test_suite() {
     TestSuite_Add_Batch(suite, (TestBatch*)TestErr_new());
     TestSuite_Add_Batch(suite, (TestBatch*)TestBB_new());
     TestSuite_Add_Batch(suite, (TestBatch*)TestStr_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestCB_new());
     TestSuite_Add_Batch(suite, (TestBatch*)TestNumUtil_new());
     TestSuite_Add_Batch(suite, (TestBatch*)TestNum_new());
     TestSuite_Add_Batch(suite, (TestBatch*)TestStrHelp_new());

http://git-wip-us.apache.org/repos/asf/lucy/blob/0806c59c/clownfish/runtime/core/Clownfish/Test/TestCharBuf.c
----------------------------------------------------------------------
diff --git a/clownfish/runtime/core/Clownfish/Test/TestCharBuf.c b/clownfish/runtime/core/Clownfish/Test/TestCharBuf.c
new file mode 100644
index 0000000..31865f9
--- /dev/null
+++ b/clownfish/runtime/core/Clownfish/Test/TestCharBuf.c
@@ -0,0 +1,300 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#define CHY_USE_SHORT_NAMES
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "charmony.h"
+
+#include "Clownfish/Test/TestCharBuf.h"
+
+#include "Clownfish/CharBuf.h"
+#include "Clownfish/Num.h"
+#include "Clownfish/String.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/VTable.h"
+
+static char smiley[] = { (char)0xE2, (char)0x98, (char)0xBA, 0 };
+static uint32_t smiley_len = 3;
+
+TestCharBuf*
+TestCB_new() {
+    return (TestCharBuf*)VTable_Make_Obj(TESTCHARBUF);
+}
+
+static CharBuf*
+S_get_cb(const char *string) {
+    return CB_new_from_utf8(string, strlen(string));
+}
+
+static String*
+S_get_str(const char *string) {
+    return Str_new_from_utf8(string, strlen(string));
+}
+
+static bool
+S_cb_equals(CharBuf *cb, String *other) {
+    String *string = CB_To_String(cb);
+    bool retval = Str_Equals(string, (Obj*)other);
+    DECREF(string);
+    return retval;
+}
+
+static void
+test_Cat(TestBatchRunner *runner) {
+    String  *wanted = Str_newf("a%s", smiley);
+    CharBuf *got    = S_get_cb("");
+
+    CB_Cat(got, wanted);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Cat");
+    DECREF(got);
+
+    got = S_get_cb("a");
+    CB_Cat_Char(got, 0x263A);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Cat_Char");
+    DECREF(got);
+
+    got = S_get_cb("a");
+    CB_Cat_UTF8(got, smiley, smiley_len);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Cat_UTF8");
+    DECREF(got);
+
+    got = S_get_cb("a");
+    CB_Cat_Trusted_UTF8(got, smiley, smiley_len);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Cat_Trusted_UTF8");
+    DECREF(got);
+
+    DECREF(wanted);
+}
+
+static void
+test_Mimic_and_Clone(TestBatchRunner *runner) {
+    String  *wanted    = S_get_str("foo");
+    CharBuf *wanted_cb = S_get_cb("foo");
+    CharBuf *got       = S_get_cb("bar");
+
+    CB_Mimic(got, (Obj*)wanted);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Mimic String");
+    DECREF(got);
+
+    got = S_get_cb("bar");
+    CB_Mimic(got, (Obj*)wanted_cb);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Mimic CharBuf");
+    DECREF(got);
+
+    got = S_get_cb("bar");
+    CB_Mimic_UTF8(got, "foo", 3);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Mimic_Str");
+    DECREF(got);
+
+    got = CB_Clone(wanted_cb);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Clone");
+    DECREF(got);
+
+    DECREF(wanted);
+}
+
+/*
+static void
+test_Truncate(TestBatchRunner *runner) {
+    String  *wanted = Str_newf("a%s", smiley);
+    CharBuf *got    = CB_newf("a%s%sb%sc", smiley, smiley, smiley);
+    CB_Truncate(got, 2);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Truncate");
+    DECREF(wanted);
+    DECREF(got);
+}
+*/
+
+static void
+test_vcatf_s(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo bar bizzle baz");
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %s baz", "bizzle");
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%s");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_null_string(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo bar [NULL] baz");
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %s baz", NULL);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%s NULL");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_str(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo bar ZEKE baz");
+    String  *catworthy = S_get_str("ZEKE");
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %o baz", catworthy);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%o CharBuf");
+    DECREF(catworthy);
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_obj(TestBatchRunner *runner) {
+    String    *wanted = S_get_str("ooga 20 booga");
+    Integer32 *i32 = Int32_new(20);
+    CharBuf   *got = S_get_cb("ooga");
+    CB_catf(got, " %o booga", i32);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%o Obj");
+    DECREF(i32);
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_null_obj(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo bar [NULL] baz");
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %o baz", NULL);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%o NULL");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_i8(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar -3 baz");
+    int8_t num = -3;
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %i8 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%i8");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_i32(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar -100000 baz");
+    int32_t num = -100000;
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %i32 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%i32");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_i64(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar -5000000000 baz");
+    int64_t num = INT64_C(-5000000000);
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %i64 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%i64");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_u8(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar 3 baz");
+    uint8_t num = 3;
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %u8 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%u8");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_u32(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar 100000 baz");
+    uint32_t num = 100000;
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %u32 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%u32");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_u64(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar 5000000000 baz");
+    uint64_t num = UINT64_C(5000000000);
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %u64 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%u64");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_f64(TestBatchRunner *runner) {
+    String *wanted;
+    char buf[64];
+    float num = 1.3f;
+    CharBuf *got = S_get_cb("foo ");
+    sprintf(buf, "foo bar %g baz", num);
+    wanted = Str_new_from_trusted_utf8(buf, strlen(buf));
+    CB_catf(got, "bar %f64 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%f64");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_x32(TestBatchRunner *runner) {
+    String *wanted;
+    char buf[64];
+    unsigned long num = INT32_MAX;
+    CharBuf *got = S_get_cb("foo ");
+#if (SIZEOF_LONG == 4)
+    sprintf(buf, "foo bar %.8lx baz", num);
+#elif (SIZEOF_INT == 4)
+    sprintf(buf, "foo bar %.8x baz", (unsigned)num);
+#endif
+    wanted = Str_new_from_trusted_utf8(buf, strlen(buf));
+    CB_catf(got, "bar %x32 baz", (uint32_t)num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%x32");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+void
+TestCB_Run_IMP(TestCharBuf *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 21);
+    test_vcatf_s(runner);
+    test_vcatf_null_string(runner);
+    test_vcatf_str(runner);
+    test_vcatf_obj(runner);
+    test_vcatf_null_obj(runner);
+    test_vcatf_i8(runner);
+    test_vcatf_i32(runner);
+    test_vcatf_i64(runner);
+    test_vcatf_u8(runner);
+    test_vcatf_u32(runner);
+    test_vcatf_u64(runner);
+    test_vcatf_f64(runner);
+    test_vcatf_x32(runner);
+    test_Cat(runner);
+    test_Mimic_and_Clone(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/0806c59c/clownfish/runtime/core/Clownfish/Test/TestCharBuf.cfh
----------------------------------------------------------------------
diff --git a/clownfish/runtime/core/Clownfish/Test/TestCharBuf.cfh b/clownfish/runtime/core/Clownfish/Test/TestCharBuf.cfh
new file mode 100644
index 0000000..3cc5d43
--- /dev/null
+++ b/clownfish/runtime/core/Clownfish/Test/TestCharBuf.cfh
@@ -0,0 +1,29 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+parcel TestClownfish;
+
+class Clownfish::Test::TestCharBuf cnick TestCB
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestCharBuf*
+    new();
+
+    void
+    Run(TestCharBuf *self, TestBatchRunner *runner);
+}
+
+