The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* 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_LUCY_CHARBUF
#define C_LUCY_VIEWCHARBUF
#define C_LUCY_ZOMBIECHARBUF
#define LUCY_USE_SHORT_NAMES
#define CHY_USE_SHORT_NAMES

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include "Lucy/Object/VTable.h"
#include "Lucy/Object/CharBuf.h"

#include "Lucy/Object/Err.h"
#include "Lucy/Store/InStream.h"
#include "Lucy/Store/OutStream.h"
#include "Lucy/Util/Memory.h"
#include "Lucy/Util/StringHelper.h"

// Helper function for throwing invalid UTF-8 error. Since THROW uses
// a CharBuf 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);

ZombieCharBuf EMPTY = { ZOMBIECHARBUF, {1}, "", 0, 0 };

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_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_new_steal_from_trusted_str(char *ptr, size_t size, size_t cap) {
    CharBuf *self = (CharBuf*)VTable_Make_Obj(CHARBUF);
    self->ptr  = ptr;
    self->size = size;
    self->cap  = cap;
    return self;
}

CharBuf*
CB_new_steal_str(char *ptr, size_t size, size_t cap) {
    if (!StrHelp_utf8_valid(ptr, size)) {
        DIE_INVALID_UTF8(ptr, size);
    }
    return CB_new_steal_from_trusted_str(ptr, size, cap);
}

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(CharBuf *self) {
    FREEMEM(self->ptr);
    SUPER_DESTROY(self, CHARBUF);
}

int32_t
CB_hash_sum(CharBuf *self) {
    uint32_t hashvalue = 5381;
    ZombieCharBuf *iterator = ZCB_WRAP(self);

    const CB_nip_one_t nip_one
        = (CB_nip_one_t)METHOD(iterator->vtable, CB, Nip_One);
    while (iterator->size) {
        uint32_t code_point = (uint32_t)nip_one((CharBuf*)iterator);
        hashvalue = ((hashvalue << 5) + hashvalue) ^ code_point;
    }

    return (int32_t) hashvalue;
}

static void
S_grow(CharBuf *self, size_t size) {
    if (size >= self->cap) {
        CB_Grow(self, size);
    }
}

char*
CB_grow(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_setf(CharBuf *self, const char *pattern, ...) {
    va_list args;
    CB_Set_Size(self, 0);
    va_start(args, pattern);
    CB_VCatF(self, pattern, args);
    va_end(args);
}

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(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_Str(self, pattern, size);
            pattern = slice_end;
        }

        if (pattern < pattern_end) {
            pattern++; // Move past '%'.

            switch (*pattern) {
                case '%': {
                        CB_Cat_Trusted_Str(self, "%", 1);
                    }
                    break;
                case 'o': {
                        Obj *obj = va_arg(args, Obj*);
                        if (!obj) {
                            CB_Cat_Trusted_Str(self, "[NULL]", 6);
                        }
                        else if (Obj_Is_A(obj, CHARBUF)) {
                            CB_Cat(self, (CharBuf*)obj);
                        }
                        else {
                            CharBuf *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, "%" I64P, val);
                        CB_Cat_Trusted_Str(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, "%" U64P, val);
                        CB_Cat_Trusted_Str(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_Str(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_Str(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_Str(self, "[NULL]", 6);
                        }
                        else {
                            size_t size = strlen(string);
                            if (StrHelp_utf8_valid(string, size)) {
                                CB_Cat_Trusted_Str(self, string, size);
                            }
                            else {
                                CB_Cat_Trusted_Str(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);
                    }
            }
        }
    }
}

CharBuf*
CB_to_string(CharBuf *self) {
    return CB_new_from_trusted_utf8(self->ptr, self->size);
}

void
CB_cat_char(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';
}

int32_t
CB_swap_chars(CharBuf *self, uint32_t match, uint32_t replacement) {
    int32_t num_swapped = 0;

    if (match > 127) {
        THROW(ERR, "match point too high: %u32", match);
    }
    else if (replacement > 127) {
        THROW(ERR, "replacement code point too high: %u32", replacement);
    }
    else {
        char *ptr = self->ptr;
        char *const limit = ptr + self->size;
        for (; ptr < limit; ptr++) {
            if (*ptr == (char)match) {
                *ptr = (char)replacement;
                num_swapped++;
            }
        }
    }

    return num_swapped;
}

int64_t
CB_to_i64(CharBuf *self) {
    return CB_BaseX_To_I64(self, 10);
}

int64_t
CB_basex_to_i64(CharBuf *self, uint32_t base) {
    ZombieCharBuf *iterator = ZCB_WRAP(self);
    int64_t retval = 0;
    bool_t is_negative = false;

    // Advance past minus sign.
    if (ZCB_Code_Point_At(iterator, 0) == '-') {
        ZCB_Nip_One(iterator);
        is_negative = true;
    }

    // Accumulate.
    while (iterator->size) {
        int32_t code_point = ZCB_Nip_One(iterator);
        if (isalnum(code_point)) {
            int32_t addend = isdigit(code_point)
                             ? code_point - '0'
                             : tolower(code_point) - 'a' + 10;
            if (addend > (int32_t)base) { break; }
            retval *= base;
            retval += addend;
        }
        else {
            break;
        }
    }

    // Apply minus sign.
    if (is_negative) { retval = 0 - retval; }

    return retval;
}

static double
S_safe_to_f64(CharBuf *self) {
    size_t amount = self->size < 511 ? self->size : 511;
    char buf[512];
    memcpy(buf, self->ptr, amount);
    buf[amount] = 0; // NULL-terminate.
    return strtod(buf, NULL);
}

double
CB_to_f64(CharBuf *self) {
    char   *end;
    double  value    = strtod(self->ptr, &end);
    size_t  consumed = end - self->ptr;
    if (consumed > self->size) { // strtod overran
        value = S_safe_to_f64(self);
    }
    return value;
}

CharBuf*
CB_to_cb8(CharBuf *self) {
    return CB_new_from_trusted_utf8(self->ptr, self->size);
}

CharBuf*
CB_clone(CharBuf *self) {
    return CB_new_from_trusted_utf8(self->ptr, self->size);
}

CharBuf*
CB_load(CharBuf *self, Obj *dump) {
    CharBuf *source = (CharBuf*)CERTIFY(dump, CHARBUF);
    UNUSED_VAR(self);
    return CB_Clone(source);
}

void
CB_serialize(CharBuf *self, OutStream *target) {
    OutStream_Write_C32(target, self->size);
    OutStream_Write_Bytes(target, self->ptr, self->size);
}

CharBuf*
CB_deserialize(CharBuf *self, InStream *instream) {
    size_t size = InStream_Read_C32(instream);
    self = self ? self : (CharBuf*)VTable_Make_Obj(CHARBUF);
    if (size >= self->cap) { S_grow(self, size); }
    InStream_Read_Bytes(instream, self->ptr, size);
    self->size = size;
    self->ptr[size] = '\0';
    if (!StrHelp_utf8_valid(self->ptr, size)) {
        DIE_INVALID_UTF8(self->ptr, size);
    }
    return self;
}

void
CB_mimic_str(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(CharBuf *self, Obj *other) {
    CharBuf *twin = (CharBuf*)CERTIFY(other, CHARBUF);
    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';
}

void
CB_cat_str(CharBuf *self, const char* ptr, size_t size) {
    if (!StrHelp_utf8_valid(ptr, size)) {
        DIE_INVALID_UTF8(ptr, size);
    }
    CB_cat_trusted_str(self, ptr, size);
}

void
CB_cat_trusted_str(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(CharBuf *self, const CharBuf *other) {
    const size_t new_size = self->size + other->size;
    if (new_size >= self->cap) {
        size_t amount = Memory_oversize(new_size, sizeof(char));
        S_grow(self, amount);
    }
    memcpy((self->ptr + self->size), other->ptr, other->size);
    self->size = new_size;
    self->ptr[new_size] = '\0';
}

bool_t
CB_starts_with(CharBuf *self, const CharBuf *prefix) {
    return CB_starts_with_str(self, prefix->ptr, prefix->size);
}

bool_t
CB_starts_with_str(CharBuf *self, const char *prefix, size_t size) {
    if (size <= self->size
        && (memcmp(self->ptr, prefix, size) == 0)
       ) {
        return true;
    }
    else {
        return false;
    }
}

bool_t
CB_equals(CharBuf *self, Obj *other) {
    CharBuf *const twin = (CharBuf*)other;
    if (twin == self)              { return true; }
    if (!Obj_Is_A(other, CHARBUF)) { return false; }
    return CB_equals_str(self, twin->ptr, twin->size);
}

int32_t
CB_compare_to(CharBuf *self, Obj *other) {
    CERTIFY(other, CHARBUF);
    return CB_compare(&self, &other);
}

bool_t
CB_equals_str(CharBuf *self, const char *ptr, size_t size) {
    if (self->size != size) {
        return false;
    }
    return (memcmp(self->ptr, ptr, self->size) == 0);
}

bool_t
CB_ends_with(CharBuf *self, const CharBuf *postfix) {
    return CB_ends_with_str(self, postfix->ptr, postfix->size);
}

bool_t
CB_ends_with_str(CharBuf *self, const char *postfix, size_t postfix_len) {
    if (postfix_len <= self->size) {
        char *start = self->ptr + self->size - postfix_len;
        if (memcmp(start, postfix, postfix_len) == 0) {
            return true;
        }
    }

    return false;
}

int64_t
CB_find(CharBuf *self, const CharBuf *substring) {
    return CB_Find_Str(self, substring->ptr, substring->size);
}

int64_t
CB_find_str(CharBuf *self, const char *ptr, size_t size) {
    ZombieCharBuf *iterator = ZCB_WRAP(self);
    int64_t location = 0;

    while (iterator->size) {
        if (ZCB_Starts_With_Str(iterator, ptr, size)) {
            return location;
        }
        ZCB_Nip(iterator, 1);
        location++;
    }

    return -1;
}

uint32_t
CB_trim(CharBuf *self) {
    return CB_Trim_Top(self) + CB_Trim_Tail(self);
}

uint32_t
CB_trim_top(CharBuf *self) {
    char     *ptr   = self->ptr;
    char     *end   = ptr + self->size;
    uint32_t  count = 0;

    while (ptr < end) {
        uint32_t code_point = StrHelp_decode_utf8_char(ptr);
        if (!StrHelp_is_whitespace(code_point)) { break; }
        ptr += StrHelp_UTF8_COUNT[*(uint8_t*)ptr];
        count++;
    }
    if (ptr > end) {
        DIE_INVALID_UTF8(self->ptr, self->size);
    }

    if (count) {
        // Copy string backwards.
        self->size = end - ptr;
        memmove(self->ptr, ptr, self->size);
    }

    return count;
}

uint32_t
CB_trim_tail(CharBuf *self) {
    uint32_t      count    = 0;
    char *const   top      = self->ptr;
    const char   *ptr      = top + self->size;
    size_t        new_size = self->size;

    while (NULL != (ptr = StrHelp_back_utf8_char(ptr, top))) {
        uint32_t code_point = StrHelp_decode_utf8_char(ptr);
        if (!StrHelp_is_whitespace(code_point)) { break; }
        new_size = ptr - top;
        count++;
    }
    self->size = new_size;

    return count;
}

size_t
CB_nip(CharBuf *self, size_t count) {
    size_t       num_nipped = 0;
    char        *ptr        = self->ptr;
    char *const  end        = ptr + self->size;
    for (; ptr < end  && count--; ptr += StrHelp_UTF8_COUNT[*(uint8_t*)ptr]) {
        num_nipped++;
    }
    if (ptr > end) {
        DIE_INVALID_UTF8(self->ptr, self->size);
    }
    self->size = end - ptr;
    memmove(self->ptr, ptr, self->size);
    return num_nipped;
}

int32_t
CB_nip_one(CharBuf *self) {
    if (self->size == 0) {
        return 0;
    }
    else {
        int32_t retval = (int32_t)StrHelp_decode_utf8_char(self->ptr);
        size_t consumed = StrHelp_UTF8_COUNT[*(uint8_t*)self->ptr];
        if (consumed > self->size) {
            DIE_INVALID_UTF8(self->ptr, self->size);
        }
        char *ptr = self->ptr + StrHelp_UTF8_COUNT[*(uint8_t*)self->ptr];
        self->size -= consumed;
        memmove(self->ptr, ptr, self->size);
        return retval;
    }
}

size_t
CB_chop(CharBuf *self, size_t count) {
    size_t      num_chopped = 0;
    char       *top         = self->ptr;
    const char *ptr         = top + self->size;
    for (num_chopped = 0; num_chopped < count; num_chopped++) {
        const char *end = ptr;
        if (NULL == (ptr = StrHelp_back_utf8_char(ptr, top))) { break; }
        self->size -= (end - ptr);
    }
    return num_chopped;
}

size_t
CB_length(CharBuf *self) {
    size_t  len  = 0;
    char   *ptr  = self->ptr;
    char   *end  = ptr + self->size;
    while (ptr < end) {
        ptr += StrHelp_UTF8_COUNT[*(uint8_t*)ptr];
        len++;
    }
    if (ptr != end) {
        DIE_INVALID_UTF8(self->ptr, self->size);
    }
    return len;
}

size_t
CB_truncate(CharBuf *self, size_t count) {
    uint32_t num_code_points;
    ZombieCharBuf *iterator = ZCB_WRAP(self);
    num_code_points = ZCB_Nip(iterator, count);
    self->size -= iterator->size;
    return num_code_points;
}

uint32_t
CB_code_point_at(CharBuf *self, size_t tick) {
    size_t count = 0;
    char *ptr = self->ptr;
    char *const end = ptr + self->size;

    for (; ptr < end; ptr += StrHelp_UTF8_COUNT[*(uint8_t*)ptr]) {
        if (count == tick) {
            if (ptr > end) {
                DIE_INVALID_UTF8(self->ptr, self->size);
            }
            return StrHelp_decode_utf8_char(ptr);
        }
        count++;
    }

    return 0;
}

uint32_t
CB_code_point_from(CharBuf *self, size_t tick) {
    size_t      count = 0;
    char       *top   = self->ptr;
    const char *ptr   = top + self->size;

    for (count = 0; count < tick; count++) {
        if (NULL == (ptr = StrHelp_back_utf8_char(ptr, top))) { return 0; }
    }
    return StrHelp_decode_utf8_char(ptr);
}

CharBuf*
CB_substring(CharBuf *self, size_t offset, size_t len) {
    ZombieCharBuf *iterator = ZCB_WRAP(self);
    char *sub_start;
    size_t byte_len;

    ZCB_Nip(iterator, offset);
    sub_start = iterator->ptr;
    ZCB_Nip(iterator, len);
    byte_len = iterator->ptr - sub_start;

    return CB_new_from_trusted_utf8(sub_start, byte_len);
}

int
CB_compare(const void *va, const void *vb) {
    const CharBuf *a = *(const CharBuf**)va;
    const CharBuf *b = *(const CharBuf**)vb;
    ZombieCharBuf *iterator_a = ZCB_WRAP(a);
    ZombieCharBuf *iterator_b = ZCB_WRAP(b);
    while (iterator_a->size && iterator_b->size) {
        int32_t code_point_a = ZCB_Nip_One(iterator_a);
        int32_t code_point_b = ZCB_Nip_One(iterator_b);
        const int32_t comparison = code_point_a - code_point_b;
        if (comparison != 0) { return comparison; }
    }
    if (iterator_a->size != iterator_b->size) {
        return iterator_a->size < iterator_b->size ? -1 : 1;
    }
    return 0;
}

bool_t
CB_less_than(const void *va, const void *vb) {
    return CB_compare(va, vb) < 0 ? 1 : 0;
}

void
CB_set_size(CharBuf *self, size_t size) {
    self->size = size;
}

size_t
CB_get_size(CharBuf *self) {
    return self->size;
}

uint8_t*
CB_get_ptr8(CharBuf *self) {
    return (uint8_t*)self->ptr;
}

/*****************************************************************/

ViewCharBuf*
ViewCB_new_from_utf8(const char *utf8, size_t size) {
    if (!StrHelp_utf8_valid(utf8, size)) {
        DIE_INVALID_UTF8(utf8, size);
    }
    return ViewCB_new_from_trusted_utf8(utf8, size);
}

ViewCharBuf*
ViewCB_new_from_trusted_utf8(const char *utf8, size_t size) {
    ViewCharBuf *self = (ViewCharBuf*)VTable_Make_Obj(VIEWCHARBUF);
    return ViewCB_init(self, utf8, size);
}

ViewCharBuf*
ViewCB_init(ViewCharBuf *self, const char *utf8, size_t size) {
    self->ptr  = (char*)utf8;
    self->size = size;
    self->cap  = 0;
    return self;
}

void
ViewCB_destroy(ViewCharBuf *self) {
    // Note that we do not free self->ptr, and that we invoke the
    // SUPER_DESTROY with CHARBUF instead of VIEWCHARBUF.
    SUPER_DESTROY(self, CHARBUF);
}

void
ViewCB_assign(ViewCharBuf *self, const CharBuf *other) {
    self->ptr  = other->ptr;
    self->size = other->size;
}

void
ViewCB_assign_str(ViewCharBuf *self, const char *utf8, size_t size) {
    if (!StrHelp_utf8_valid(utf8, size)) {
        DIE_INVALID_UTF8(utf8, size);
    }
    self->ptr  = (char*)utf8;
    self->size = size;
}

void
ViewCB_assign_trusted_str(ViewCharBuf *self, const char *utf8, size_t size) {
    self->ptr  = (char*)utf8;
    self->size = size;
}

uint32_t
ViewCB_trim_top(ViewCharBuf *self) {
    uint32_t  count = 0;
    char     *ptr   = self->ptr;
    char     *end   = ptr + self->size;

    while (ptr < end) {
        uint32_t code_point = StrHelp_decode_utf8_char(ptr);
        if (!StrHelp_is_whitespace(code_point)) { break; }
        ptr += StrHelp_UTF8_COUNT[*(uint8_t*)ptr];
        count++;
    }

    if (count) {
        if (ptr > end) {
            DIE_INVALID_UTF8(self->ptr, self->size);
        }
        self->size = end - ptr;
        self->ptr  = ptr;
    }

    return count;
}

size_t
ViewCB_nip(ViewCharBuf *self, size_t count) {
    size_t  num_nipped;
    char   *ptr = self->ptr;
    char   *end = ptr + self->size;
    for (num_nipped = 0;
         ptr < end && count--;
         ptr += StrHelp_UTF8_COUNT[*(uint8_t*)ptr]
        ) {
        num_nipped++;
    }
    if (ptr > end) {
        DIE_INVALID_UTF8(self->ptr, self->size);
    }
    self->size = end - ptr;
    self->ptr  = ptr;
    return num_nipped;
}

int32_t
ViewCB_nip_one(ViewCharBuf *self) {
    if (self->size == 0) {
        return 0;
    }
    else {
        int32_t retval = (int32_t)StrHelp_decode_utf8_char(self->ptr);
        size_t consumed = StrHelp_UTF8_COUNT[*(uint8_t*)self->ptr];
        if (consumed > self->size) {
            DIE_INVALID_UTF8(self->ptr, self->size);
        }
        self->ptr  += consumed;
        self->size -= consumed;
        return retval;
    }
}

char*
ViewCB_grow(ViewCharBuf *self, size_t size) {
    UNUSED_VAR(size);
    THROW(ERR, "Can't grow a ViewCharBuf ('%o')", self);
    UNREACHABLE_RETURN(char*);
}

/*****************************************************************/

ZombieCharBuf*
ZCB_new(void *allocation) {
    static char empty_string[] = "";
    ZombieCharBuf *self = (ZombieCharBuf*)allocation;
    self->ref.count    = 1;
    self->vtable       = ZOMBIECHARBUF;
    self->cap          = 0;
    self->size         = 0;
    self->ptr          = empty_string;
    return self;
}

ZombieCharBuf*
ZCB_newf(void *allocation, size_t alloc_size, const char *pattern, ...) {
    ZombieCharBuf *self = (ZombieCharBuf*)allocation;

    self->ref.count    = 1;
    self->vtable       = ZOMBIECHARBUF;
    self->cap          = alloc_size - sizeof(ZombieCharBuf);
    self->size         = 0;
    self->ptr          = ((char*)allocation) + sizeof(ZombieCharBuf);

    va_list args;
    va_start(args, pattern);
    ZCB_VCatF(self, pattern, args);
    va_end(args);

    return self;
}

ZombieCharBuf*
ZCB_wrap_str(void *allocation, const char *ptr, size_t size) {
    ZombieCharBuf *self = (ZombieCharBuf*)allocation;
    self->ref.count    = 1;
    self->vtable       = ZOMBIECHARBUF;
    self->cap          = 0;
    self->size         = size;
    self->ptr          = (char*)ptr;
    return self;
}

ZombieCharBuf*
ZCB_wrap(void *allocation, const CharBuf *source) {
    return ZCB_wrap_str(allocation, source->ptr, source->size);
}

size_t
ZCB_size() {
    return sizeof(ZombieCharBuf);
}

void
ZCB_destroy(ZombieCharBuf *self) {
    THROW(ERR, "Can't destroy a ZombieCharBuf ('%o')", self);
}