<commit_before>//

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

#include "bytes.h"
#include "memory_.h"
#include "exceptions.h"
#include "string_.h"
#include "globals.h"
#include "hutil.h"
#include "threading.h"
#include "int.h"
#include "autogen/hilti-hlt.h"

// Bytes object cannot be changed anymore.
static const int _BYTES_FLAG_FROZEN = 1;

// Data of this node contains a separator object. It's fine to cast to
// __hlt_bytes_object in that case. The other data pointers inside the bytes
// object aren't valid in this case, and set to null.
static const int _BYTES_FLAG_OBJECT = 2;

struct __hlt_bytes {
    __hlt_gchdr __gchdr;       // Header for memory management.
    __hlt_thread_mgr_blockable blockable; // For blocking until changed.
    int8_t flags;              // Flags as combiniation of _BYTES_FLAG_* values.
    struct __hlt_bytes* next;  // Next part. Ref counted.
    hlt_bytes_size offset;     // The offset of this chunks first byte relative to the beginning of the bytes object it's part of.
    int8_t* start;             // Pointer to first data byte.
    int8_t* end;               // Pointer to one after last data byte used so far.
    int8_t* reserved;          // Pointer to one after the last data byte available.
    int8_t* to_free;           // Need to free data pointed to when dtoring.
    int8_t data[0];            // Inline data starts here if free is zero.
};

// Specialized bytes object storing a separator object.
struct __hlt_bytes_object {
    struct __hlt_bytes b;       // Common header.
    const hlt_type_info* type;  // Type of the object;
    char object[0];             // Object's storage starts here, with size determined by type.
};

typedef struct __hlt_bytes_object __hlt_bytes_object;

static hlt_iterator_bytes GenericEndPos = { 0, 0 };

static inline hlt_bytes_size min(hlt_bytes_size a, hlt_bytes_size b)
{
    return a < b ? a : b;
}

static inline __hlt_bytes_object* __get_object(const hlt_bytes* b)
{
    return (b->flags & _BYTES_FLAG_OBJECT) ? (__hlt_bytes_object*) b : 0;
}

static inline hlt_bytes* __tail(hlt_bytes* b, int8_t consider_object)
{
    if ( ! b )
        return 0;

    while ( b->next ) {
        if ( ! consider_object && __get_object(b->next) )
            break;

        b = b->next;
    }

    return b;
}

static inline int8_t __at_object(const hlt_iterator_bytes i)
{
    return i.bytes && __get_object(i.bytes) != 0;
}

static inline int8_t __is_end(const hlt_iterator_bytes p)
{
    return p.bytes == 0 || (p.bytes == __tail(p.bytes, false) && p.cur >= p.bytes->end) || __at_object(p);
}

static inline int8_t __is_frozen(const hlt_bytes* b)
{
    return b ? (b->flags & _BYTES_FLAG_FROZEN) : false;
}

// For debugging.
static void __print_bytes(const char* prefix, const hlt_bytes* b)
{
    if ( ! b ) {
        fprintf(stderr, "%s: (null)\n", prefix);
        return;
    }

    fprintf(stderr, "%s: %p b( ", prefix, b);
    for ( ; b; b = b->next ) {
        fprintf(stderr, "#%ld:%p-%p \"", b->end - b->start, b->start, b->end);

        if ( __get_object(b) ) {
            fprintf(stderr, " [[object]] ");
            continue;
        }

        for ( int i = 0; i < min(b->end - b->start, 20); i++) {
            if ( isprint(b->start[i]) )
                fprintf(stderr, "%c", b->start[i]);
            else
                fprintf(stderr, "\\x%2x", b->start[i]);
        }

        if ( (b->end - b->start) > 20 )
            fprintf(stderr, "...");

        fprintf(stderr, "\" ");
    }

    fprintf(stderr, ")\n");
}

static void __print_iterator(const char* prefix, hlt_iterator_bytes i)
{
    fprintf(stderr, "%s: i(#%p@%lu) is_end=%d at_object=%d\n", prefix, i.bytes, i.bytes ? i.cur - i.bytes->start : 0, __is_end(i), __at_object(i));
    __print_bytes("  -> ", i.bytes);
}

static inline int8_t __is_empty(const hlt_bytes* b, int8_t consider_objects)
{
    for ( const hlt_bytes* c = b; c; c = c->next ) {
        if ( __get_object(c) )
            return ! consider_objects;

        if ( c->start != c->end )
            return 0;
    }

    return 1;
}

// TODO: Frequent enough to want a double-linked list?
static inline hlt_bytes* __pred(hlt_bytes* b, hlt_bytes* p)
{
    for ( ; b && b->next != p; b = b->next )
        ;

    return b;
}

// Does not ref the iterator.
static inline hlt_iterator_bytes __create_iterator(hlt_bytes* bytes, int8_t* cur)
{
    hlt_iterator_bytes i = { bytes, cur };
    return i;
}

// This version does not adjust the reference count and must be called only
// when the potentiall changed iterator will not be visible to the HILTI
// layer.
static inline void __normalize_iter(hlt_iterator_bytes* pos)
{
    if ( ! pos->bytes || __at_object(*pos) )
        return;

    // If the pos was previously an end position but now new data has been
    // added, adjust it so that it's pointing to the next byte. Note we do
    // not adjust the reference count in this version.
    while ( pos->cur >= pos->bytes->end && pos->bytes->next && ! __at_object(*pos) ) {
        pos->bytes = pos->bytes->next;
        pos->cur = pos->bytes->start;
    }
}

// This version adjust the reference count and should be called only when the
// potentiall changed iterator will be visible to the HILTI layer.
static inline void __normalize_iter_hilti(hlt_iterator_bytes* pos)
{
    if ( ! pos->bytes || __at_object(*pos) )
        return;

    // If the pos was previously an end position but now new data has been
    // added, adjust it so that it's pointing to the next byte.
    while ( pos->cur >= pos->bytes->end && pos->bytes->next ) {
        GC_ASSIGN(pos->bytes, pos->bytes->next, hlt_bytes);
        pos->cur = pos->bytes->start;
    }
}

// c already ref'ed.
static void __add_chunk(hlt_bytes* tail, hlt_bytes* c)
{
    assert(c);
    assert(tail);
    assert(! tail->next);
    assert(! __is_frozen(tail));

    tail->next = c; // Consumes ref cnt.
    c->offset = tail->offset + (__get_object(tail) ? 0 : tail->end - tail->start);
}

static hlt_bytes* __hlt_bytes_new(const int8_t* data, hlt_bytes_size len, hlt_bytes_size reserve)
{
    if ( ! reserve )
        reserve = (len ? len : 32);

    assert(reserve >= len);

    hlt_bytes* b = GC_NEW_CUSTOM_SIZE_NO_INIT(hlt_bytes, sizeof(hlt_bytes) + reserve);
    b->flags = 0;
    b->next = 0;
    b->offset = 0;
    b->start = b->data;
    b->end = b->start + len;
    b->reserved = b->start + reserve;
    b->to_free = 0;

    hlt_thread_mgr_blockable_init(&b->blockable);

    if ( data )
        memcpy(b->data, data, len);

    return b;
}

static hlt_bytes* __hlt_bytes_new_object(const hlt_type_info* type, void* obj, hlt_execution_context* ctx)
{
    __hlt_bytes_object* b = GC_NEW_CUSTOM_SIZE(hlt_bytes, sizeof(__hlt_bytes_object) + type->size);
    b->b.next = 0;
    b->b.flags = _BYTES_FLAG_OBJECT;
    b->b.offset = 0;
    b->type = type;

    hlt_thread_mgr_blockable_init(&b->b.blockable);

    if ( obj ) {
        memcpy(&b->object, obj, type->size);
        GC_CCTOR_GENERIC(&b->object, type);
    }

    return &b->b;
}

static hlt_bytes* __hlt_bytes_new_reuse(int8_t* data, hlt_bytes_size len)
{
    hlt_bytes* b = GC_NEW_NO_INIT(hlt_bytes);
    b->flags = 0;
    b->offset = 0;
    b->next = 0;
    b->start = data;
    b->end = data + len;
    b->reserved = data + len;
    b->to_free = data;

    hlt_thread_mgr_blockable_init(&b->blockable);

    return b;
}

hlt_bytes_size __hlt_bytes_len(hlt_bytes* b)
{
    hlt_bytes_size len = 0;

    for ( ; b && ! __get_object(b) ; b = b->next )
        len += (b->end - b->start);

    return len;
}

#if 0

// Turns out that we don't need this with the current object semantics. Note,
// if reenabling this code later: it hasn't been fully tested and may not
// work right in all cases.

// Duplicates b1 and, optionally, b2 into a preallocated bytes object. It's
// ok if dst isn't large enough, then we'll add chunks as necessary. The
// duplication includes all included separator objects.
static inline void __hlt_bytes_duplicate_into_helper(hlt_bytes* dst, hlt_bytes* src, int8_t* start, hlt_execution_context* ctx)
{
    // Copy data into object as long as possible.

    __hlt_bytes_object* o;
    hlt_bytes* b;

    hlt_bytes_size available = (dst->reserved - dst->start);
    int8_t* p = dst->start;

    for ( b = src; b && available && ! __get_object(b); b = b->next, start = 0 ) {

        if ( ! start )
            start = b->start;

        assert(start);

        hlt_bytes_size len = (b->end - b->start);
        hlt_bytes_size n = min(available, len);
        memcpy(p, start, n);
        dst->end = p + n;

        p += n;
        available -= n;

        if ( len != n ) {
            // Not enough space.
            start += n;
            break;
        }
    }

    if ( ! b )
        // All copied. Done.
        return;

    while ( b && (o = __get_object(b)) ) {
        // Duplicate object.
        void* p = &o->object;
        __hlt_bytes_object* no = (__hlt_bytes_object*)__hlt_bytes_new_object(o->type, &p, ctx);
        __add_chunk(dst, &no->b);
        dst = &no->b;
        b = b->next;
        start = b->start;
    }

    if ( ! b )
        // All copied now. Done.
        return;

    // Create a new bytes object, copy the rest in there, and append it.
    hlt_bytes_size len = __hlt_bytes_len(b);

    if ( len ) {
        hlt_bytes* ext = __hlt_bytes_new(0, len, 0);
        __add_chunk(dst, ext);
        __hlt_bytes_duplicate_into_helper(ext, b, start, ctx);
    }
}

static inline void __hlt_bytes_duplicate_into(hlt_bytes* dst, hlt_bytes* src, hlt_execution_context* ctx)
{
    __hlt_bytes_duplicate_into_helper(dst, src, src->start, ctx);
}

static inline void __hlt_bytes_duplicate_into_2(hlt_bytes* dst, hlt_bytes* src1, hlt_bytes* src2, hlt_execution_context* ctx)
{
    // We temporarily connect the two.
    hlt_bytes* t = __tail(src1, true);
    t->next = src2;
    __hlt_bytes_duplicate_into_helper(dst, src1, src1->start, ctx);
    t->next = 0;
}

#endif

void hlt_bytes_dtor(hlt_type_info* ti, hlt_bytes* b)
{
    b->start = b->end = 0;
    GC_CLEAR(b->next, hlt_bytes);

    __hlt_bytes_object* obj = __get_object(b);

    if ( obj ) {
        GC_DTOR_GENERIC(&obj->object, obj->type);
    }

    else if ( b->to_free )
        hlt_free(b->to_free);
}

void hlt_iterator_bytes_dtor(hlt_type_info* ti, hlt_iterator_bytes* p)
{
    if ( ! p->bytes )
        return;

    GC_CLEAR(p->bytes, hlt_bytes);
}

void hlt_iterator_bytes_cctor(hlt_type_info* ti, hlt_iterator_bytes* p)
{
    if ( ! p->bytes )
        return;

    GC_CCTOR(p->bytes, hlt_bytes)
}

hlt_bytes* hlt_bytes_new(hlt_exception** excpt, hlt_execution_context* ctx)
{
    return __hlt_bytes_new(0, 0, 0);
}

hlt_bytes* hlt_bytes_new_from_data(int8_t* data, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx)
{
    return __hlt_bytes_new_reuse(data, len);
}

hlt_bytes* hlt_bytes_new_from_data_copy(const int8_t* data, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx)
{
    return __hlt_bytes_new(data, len, 0);
}

void* hlt_bytes_clone_alloc(const hlt_type_info* ti, void* srcp, __hlt_clone_state* cstate, hlt_exception** excpt, hlt_execution_context* ctx)
{
    hlt_bytes* src = *(hlt_bytes**)srcp;

    __hlt_bytes_object *so = __get_object(src);

    if ( so )
        return __hlt_bytes_new_object(so->type, 0, ctx);
    else
        return __hlt_bytes_new(0, src->end - src->start, 0);
}

void hlt_bytes_clone_init(void* dstp, const hlt_type_info* ti, void* srcp, __hlt_clone_state* cstate, hlt_exception** excpt, hlt_execution_context* ctx)
{
    hlt_bytes* src = *(hlt_bytes**)srcp;
    hlt_bytes* dst = *(hlt_bytes**)dstp;

    assert(src && dst);

    dst->flags = src->flags;
    dst->offset = src->offset;

    int first = 1;

    for ( ; src; src = src->next ) {

        hlt_bytes* b = 0;

        __hlt_bytes_object *so = __get_object(src);

        if ( so ) {
            if ( first ) {
                // Already allocated.
                __hlt_bytes_object* no = (__hlt_bytes_object*) dst;
                memcpy(&no->object, &so->object, no->type->size);
                GC_CCTOR_GENERIC(&no->object, no->type);
                b = &no->b;
            }

            else {
                void* p = &so->object;
                __hlt_bytes_object* no = (__hlt_bytes_object*)__hlt_bytes_new_object(so->type, p, ctx);
                b = &no->b;
            }
        }

        else {
            if  ( first ) {
                // Already allocated.
                hlt_bytes_size n = (src->end - src->start);
                memcpy(dst->start, src->start, n);
                b = dst;
            }

            else {
                hlt_bytes_size n = (src->end - src->start);
                b = __hlt_bytes_new(src->start, n, 0);
            }
        }

        if ( ! first ) {
            __add_chunk(dst, b);
            dst = b;
        }

        else
            first = 0;

    }
}

// Returns the number of bytes stored.
hlt_bytes_size hlt_bytes_len(hlt_bytes* b, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return 0;
    }

    return __hlt_bytes_len(b);
}

// Returns true if empty.
int8_t hlt_bytes_empty(hlt_bytes* b, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return 0;
    }

    return __is_empty(b, false);
}

void __hlt_bytes_append_raw(hlt_bytes* b, int8_t* raw, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx, int8_t reuse)
{
    if ( ! len )
        return;

    if ( __is_frozen(b) ) {
        hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        return;
    }

    // TODO: Copy into available space if large enough.

    hlt_bytes* c;

    if ( reuse )
        c = __hlt_bytes_new_reuse(raw, len);
    else
        c = __hlt_bytes_new(raw, len, 0);

    __add_chunk(__tail(b, true), c);

    hlt_thread_mgr_unblock(&b->blockable, ctx);
}

// Appends one Bytes object to another.
void hlt_bytes_append(hlt_bytes* b, hlt_bytes* other, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return;
    }

    if ( __is_frozen(b) ) {
        hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        return;
    }

    hlt_bytes_size len = hlt_bytes_len(other, excpt, ctx);

    if ( ! len )
        return;

    // TODO: Copy into available space if large enough.

    hlt_bytes* dst = __hlt_bytes_new(0, len, 0);
    int8_t* p = dst->start;

    for ( ; other && ! __get_object(other); other = other->next ) {
        hlt_bytes_size n = other->end - other->start;
        memcpy(p, other->start, n);
        p += n;
    }

    __add_chunk(__tail(b, true), dst);

    hlt_thread_mgr_unblock(&b->blockable, ctx);
}

void hlt_bytes_append_raw(hlt_bytes* b, int8_t* raw, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return;
    }

    __hlt_bytes_append_raw(b, raw, len, excpt, ctx, 1);
}

void hlt_bytes_append_raw_copy(hlt_bytes* b, int8_t* raw, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return;
    }

    __hlt_bytes_append_raw(b, raw, len, excpt, ctx, 0);
}

hlt_bytes* hlt_bytes_concat(hlt_bytes* b1, hlt_bytes* b2, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! (b1 && b2) ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return 0;
    }

    hlt_bytes_size len1 = hlt_bytes_len(b1, excpt, ctx);
    hlt_bytes_size len2 = hlt_bytes_len(b2, excpt, ctx);

    hlt_bytes* dst = __hlt_bytes_new(0, len1 + len2, 0);

    int8_t* p = dst->start;

    for ( ; b1 && ! __get_object(b1); b1 = b1->next ) {
        hlt_bytes_size n = b1->end - b1->start;
        memcpy(p, b1->start, n);
        p += n;
    }

    for ( ; b2 && ! __get_object(b2); b2 = b2->next ) {
        hlt_bytes_size n = b2->end - b2->start;
        memcpy(p, b2->start, n);
        p += n;
    }

    return dst;
}

// Doesn't ref the iterator.
void __hlt_bytes_find_byte(hlt_iterator_bytes* p, hlt_bytes* b, int8_t chr, hlt_exception** excpt, hlt_execution_context* ctx)
{
    for ( ; b && ! __get_object(b); b = b->next ) {
        int8_t* c = memchr(b->start, chr, b->end - b->start);

        if ( c ) {
            *p = __create_iterator(b, c);
            return;
        }
    }

    *p = GenericEndPos;
}

// Doesn't ref the iterator.
void __hlt_bytes_end(hlt_iterator_bytes* p, hlt_bytes* b, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        *p = GenericEndPos;
        return;
    }

    p->bytes = __tail(b, false);
    p->cur = p->bytes->end;
}

// Doesn't ref the iterator.
void __hlt_bytes_begin(hlt_iterator_bytes* p, hlt_bytes* b, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_empty(b, false) ) {
        __hlt_bytes_end(p, b, excpt, ctx);
        return;
    }

    p->bytes = b;
    p->cur = b->start;
    __normalize_iter(p);
}

// Doesn't ref the iterator.
void __hlt_bytes_find_byte_from(hlt_iterator_bytes* p, hlt_iterator_bytes i, int8_t chr, hlt_exception** excpt, hlt_execution_context* ctx)
{
    // First chunk.
    hlt_bytes* b = i.bytes;
    int8_t* c = memchr(i.cur, chr, b->end - i.cur);

    if ( ! c ) {
        // Subsequent chunks.
        for ( b = i.bytes->next; b && ! __get_object(b); b = b->next ) {
            int8_t* c = memchr(b->start, chr, b->end - b->start);

            if ( c )
                break;
        }
    }

    if ( c )
        *p = __create_iterator(b, c);
    else
        __hlt_bytes_end(p, i.bytes, excpt, ctx);
}

hlt_iterator_bytes hlt_bytes_find_byte(hlt_bytes* b, int8_t chr, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return GenericEndPos;
    }

    hlt_iterator_bytes p;
    __hlt_bytes_find_byte(&p, b, chr, excpt, ctx);
    GC_CCTOR(p, hlt_iterator_bytes);
    return p;
}

void __hlt_iterator_bytes_incr(hlt_iterator_bytes* p, hlt_exception** excpt, hlt_execution_context* ctx, int8_t adj_ref)
{
    if ( __is_end(*p) )
        // Fail silently.
        return;

    // Can we stay inside the same chunk?
    if ( p->cur + 1 < p->bytes->end ) {
        ++p->cur;
        return;
    }

    if ( ! p->bytes->next ) {
        // End reached.
        p->cur = p->bytes->end;
        return;
    }

    // Switch chunk.
    if ( adj_ref ) {
        GC_ASSIGN(p->bytes, p->bytes->next, hlt_bytes);
    }

    else
        p->bytes = p->bytes->next;

    p->cur = p->bytes->start;
}

void __hlt_iterator_bytes_incr_by(hlt_iterator_bytes* p, int64_t n, hlt_exception** excpt, hlt_execution_context* ctx, int8_t adj_ref)
{
    if ( __is_end(*p) )
        // Fail silently.
        return;

    while ( 1 ) {

        // Can we stay inside the same chunk?
        if ( p->cur + n < p->bytes->end ) {
            p->cur += n;
            return;
        }

        if ( ! p->bytes->next || __get_object(p->bytes) ) {
            // End reached.
            p->cur = p->bytes->end;
            return;
        }

        // Switch chunk.
        n -= (p->bytes->end - p->cur);

        if ( adj_ref ) {
            GC_ASSIGN(p->bytes, p->bytes->next, hlt_bytes);
        }

        else
            p->bytes = p->bytes->next;

        p->cur = p->bytes->start;
    }

    // Cannot reach.
    assert(false);
}

int8_t __hlt_iterator_bytes_deref(hlt_iterator_bytes p, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_end(p) ) {
        // Position is out range.
        hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        return 0;
    }

    return *p.cur;
}

int8_t __hlt_iterator_bytes_eq(hlt_iterator_bytes p1, hlt_iterator_bytes p2, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_end(p1) && __is_end(p2) )
        return 1;

    return p1.bytes == p2.bytes && p1.cur == p2.cur;
}

// Returns the number of bytes from p1 to p2 (not counting p2).
hlt_bytes_size __hlt_iterator_bytes_diff(hlt_iterator_bytes p1, hlt_iterator_bytes p2, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_end(p1) ) {
        if ( ! __is_end(p2) && ! (__at_object(p1) || __at_object(p2)) ) {
            // Invalid starting position.
            hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        }

        return 0;
    }

    if ( __is_end(p2) )
        return hlt_bytes_len(p1.bytes, excpt, ctx) - (p1.cur - p1.bytes->start);

    if ( __hlt_iterator_bytes_eq(p1, p2, excpt, ctx) )
        return 0;

    // Special case: both inside same chunk.
    if ( p1.bytes == p2.bytes ) {
        hlt_bytes_size n = p2.cur - p1.cur;
        return (n > 0 ? n : 0);
    }

    // Count first bytes.
    hlt_bytes_size n = p1.bytes->end - p1.cur;

    // Count intermediary bytes.

    int8_t p2_is_end = __is_end(p2);
    hlt_bytes* c = p1.bytes->next;

    for ( ; c != p2.bytes || (p2_is_end && c && ! __get_object(c)); c = c->next )
        n += (c->end - c->start);

    // Count final bytes.

    if ( c )
        n += (p2.cur - p2.bytes->start);

    else if ( ! p2_is_end ) {
        // Incompatible iterators.
        hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        return 0;
    }

    return n;
}

// Doesn't ref the iterator.
int8_t __hlt_bytes_find_bytes(hlt_iterator_bytes* p, hlt_bytes* b, hlt_bytes* other, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_empty(other, false) ) {
        // Empty pattern returns start position.
        __hlt_bytes_begin(p, b, excpt, ctx);
        return 1;
    }

    hlt_iterator_bytes begin;
    hlt_iterator_bytes end;
    __hlt_bytes_begin(&begin, other, excpt, ctx);
    __hlt_bytes_end(&end, other, excpt, ctx);


    int8_t chr = *begin.cur;

    hlt_iterator_bytes i;
    __hlt_bytes_begin(&i, b, excpt, ctx);

    while ( 1 ) {
        __hlt_bytes_find_byte_from(p, i, chr, excpt, ctx);

        if ( __is_end(*p) ) {
            // Not found.
            break;
        }<commit_msg>Using bytes.index to implement bytes.diff.<commit_after>//

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

#include "bytes.h"
#include "memory_.h"
#include "exceptions.h"
#include "string_.h"
#include "globals.h"
#include "hutil.h"
#include "threading.h"
#include "int.h"
#include "autogen/hilti-hlt.h"

// Bytes object cannot be changed anymore.
static const int _BYTES_FLAG_FROZEN = 1;

// Data of this node contains a separator object. It's fine to cast to
// __hlt_bytes_object in that case. The other data pointers inside the bytes
// object aren't valid in this case, and set to null.
static const int _BYTES_FLAG_OBJECT = 2;

struct __hlt_bytes {
    __hlt_gchdr __gchdr;       // Header for memory management.
    __hlt_thread_mgr_blockable blockable; // For blocking until changed.
    int8_t flags;              // Flags as combiniation of _BYTES_FLAG_* values.
    struct __hlt_bytes* next;  // Next part. Ref counted.
    hlt_bytes_size offset;     // The offset of this chunks first byte relative to the beginning of the bytes object it's part of.
    int8_t* start;             // Pointer to first data byte.
    int8_t* end;               // Pointer to one after last data byte used so far.
    int8_t* reserved;          // Pointer to one after the last data byte available.
    int8_t* to_free;           // Need to free data pointed to when dtoring.
    int8_t data[0];            // Inline data starts here if free is zero.
};

// Specialized bytes object storing a separator object.
struct __hlt_bytes_object {
    struct __hlt_bytes b;       // Common header.
    const hlt_type_info* type;  // Type of the object;
    char object[0];             // Object's storage starts here, with size determined by type.
};

typedef struct __hlt_bytes_object __hlt_bytes_object;

static hlt_iterator_bytes GenericEndPos = { 0, 0 };

static inline hlt_bytes_size min(hlt_bytes_size a, hlt_bytes_size b)
{
    return a < b ? a : b;
}

static inline __hlt_bytes_object* __get_object(const hlt_bytes* b)
{
    return (b->flags & _BYTES_FLAG_OBJECT) ? (__hlt_bytes_object*) b : 0;
}

static inline hlt_bytes* __tail(hlt_bytes* b, int8_t consider_object)
{
    if ( ! b )
        return 0;

    while ( b->next ) {
        if ( ! consider_object && __get_object(b->next) )
            break;

        b = b->next;
    }

    return b;
}

static inline int8_t __at_object(const hlt_iterator_bytes i)
{
    return i.bytes && __get_object(i.bytes) != 0;
}

static inline int8_t __is_end(const hlt_iterator_bytes p)
{
    return p.bytes == 0 || (p.bytes == __tail(p.bytes, false) && p.cur >= p.bytes->end) || __at_object(p);
}

static inline int8_t __is_frozen(const hlt_bytes* b)
{
    return b ? (b->flags & _BYTES_FLAG_FROZEN) : false;
}

// For debugging.
static void __print_bytes(const char* prefix, const hlt_bytes* b)
{
    if ( ! b ) {
        fprintf(stderr, "%s: (null)\n", prefix);
        return;
    }

    fprintf(stderr, "%s: %p b( ", prefix, b);
    for ( ; b; b = b->next ) {
        fprintf(stderr, "#%ld:%p-%p \"", b->end - b->start, b->start, b->end);

        if ( __get_object(b) ) {
            fprintf(stderr, " [[object]] ");
            continue;
        }

        for ( int i = 0; i < min(b->end - b->start, 20); i++) {
            if ( isprint(b->start[i]) )
                fprintf(stderr, "%c", b->start[i]);
            else
                fprintf(stderr, "\\x%2x", b->start[i]);
        }

        if ( (b->end - b->start) > 20 )
            fprintf(stderr, "...");

        fprintf(stderr, "\" ");
    }

    fprintf(stderr, ")\n");
}

static void __print_iterator(const char* prefix, hlt_iterator_bytes i)
{
    fprintf(stderr, "%s: i(#%p@%lu) is_end=%d at_object=%d\n", prefix, i.bytes, i.bytes ? i.cur - i.bytes->start : 0, __is_end(i), __at_object(i));
    __print_bytes("  -> ", i.bytes);
}

static inline int8_t __is_empty(const hlt_bytes* b, int8_t consider_objects)
{
    for ( const hlt_bytes* c = b; c; c = c->next ) {
        if ( __get_object(c) )
            return ! consider_objects;

        if ( c->start != c->end )
            return 0;
    }

    return 1;
}

// TODO: Frequent enough to want a double-linked list?
static inline hlt_bytes* __pred(hlt_bytes* b, hlt_bytes* p)
{
    for ( ; b && b->next != p; b = b->next )
        ;

    return b;
}

// Does not ref the iterator.
static inline hlt_iterator_bytes __create_iterator(hlt_bytes* bytes, int8_t* cur)
{
    hlt_iterator_bytes i = { bytes, cur };
    return i;
}

// This version does not adjust the reference count and must be called only
// when the potentiall changed iterator will not be visible to the HILTI
// layer.
static inline void __normalize_iter(hlt_iterator_bytes* pos)
{
    if ( ! pos->bytes || __at_object(*pos) )
        return;

    // If the pos was previously an end position but now new data has been
    // added, adjust it so that it's pointing to the next byte. Note we do
    // not adjust the reference count in this version.
    while ( pos->cur >= pos->bytes->end && pos->bytes->next && ! __at_object(*pos) ) {
        pos->bytes = pos->bytes->next;
        pos->cur = pos->bytes->start;
    }
}

// This version adjust the reference count and should be called only when the
// potentiall changed iterator will be visible to the HILTI layer.
static inline void __normalize_iter_hilti(hlt_iterator_bytes* pos)
{
    if ( ! pos->bytes || __at_object(*pos) )
        return;

    // If the pos was previously an end position but now new data has been
    // added, adjust it so that it's pointing to the next byte.
    while ( pos->cur >= pos->bytes->end && pos->bytes->next ) {
        GC_ASSIGN(pos->bytes, pos->bytes->next, hlt_bytes);
        pos->cur = pos->bytes->start;
    }
}

// c already ref'ed.
static void __add_chunk(hlt_bytes* tail, hlt_bytes* c)
{
    assert(c);
    assert(tail);
    assert(! tail->next);
    assert(! __is_frozen(tail));

    tail->next = c; // Consumes ref cnt.
    c->offset = tail->offset + (__get_object(tail) ? 0 : tail->end - tail->start);
}

static hlt_bytes* __hlt_bytes_new(const int8_t* data, hlt_bytes_size len, hlt_bytes_size reserve)
{
    if ( ! reserve )
        reserve = (len ? len : 32);

    assert(reserve >= len);

    hlt_bytes* b = GC_NEW_CUSTOM_SIZE_NO_INIT(hlt_bytes, sizeof(hlt_bytes) + reserve);
    b->flags = 0;
    b->next = 0;
    b->offset = 0;
    b->start = b->data;
    b->end = b->start + len;
    b->reserved = b->start + reserve;
    b->to_free = 0;

    hlt_thread_mgr_blockable_init(&b->blockable);

    if ( data )
        memcpy(b->data, data, len);

    return b;
}

static hlt_bytes* __hlt_bytes_new_object(const hlt_type_info* type, void* obj, hlt_execution_context* ctx)
{
    __hlt_bytes_object* b = GC_NEW_CUSTOM_SIZE(hlt_bytes, sizeof(__hlt_bytes_object) + type->size);
    b->b.next = 0;
    b->b.flags = _BYTES_FLAG_OBJECT;
    b->b.offset = 0;
    b->type = type;

    hlt_thread_mgr_blockable_init(&b->b.blockable);

    if ( obj ) {
        memcpy(&b->object, obj, type->size);
        GC_CCTOR_GENERIC(&b->object, type);
    }

    return &b->b;
}

static hlt_bytes* __hlt_bytes_new_reuse(int8_t* data, hlt_bytes_size len)
{
    hlt_bytes* b = GC_NEW_NO_INIT(hlt_bytes);
    b->flags = 0;
    b->offset = 0;
    b->next = 0;
    b->start = data;
    b->end = data + len;
    b->reserved = data + len;
    b->to_free = data;

    hlt_thread_mgr_blockable_init(&b->blockable);

    return b;
}

hlt_bytes_size __hlt_bytes_len(hlt_bytes* b)
{
    hlt_bytes_size len = 0;

    for ( ; b && ! __get_object(b) ; b = b->next )
        len += (b->end - b->start);

    return len;
}

#if 0

// Turns out that we don't need this with the current object semantics. Note,
// if reenabling this code later: it hasn't been fully tested and may not
// work right in all cases.

// Duplicates b1 and, optionally, b2 into a preallocated bytes object. It's
// ok if dst isn't large enough, then we'll add chunks as necessary. The
// duplication includes all included separator objects.
static inline void __hlt_bytes_duplicate_into_helper(hlt_bytes* dst, hlt_bytes* src, int8_t* start, hlt_execution_context* ctx)
{
    // Copy data into object as long as possible.

    __hlt_bytes_object* o;
    hlt_bytes* b;

    hlt_bytes_size available = (dst->reserved - dst->start);
    int8_t* p = dst->start;

    for ( b = src; b && available && ! __get_object(b); b = b->next, start = 0 ) {

        if ( ! start )
            start = b->start;

        assert(start);

        hlt_bytes_size len = (b->end - b->start);
        hlt_bytes_size n = min(available, len);
        memcpy(p, start, n);
        dst->end = p + n;

        p += n;
        available -= n;

        if ( len != n ) {
            // Not enough space.
            start += n;
            break;
        }
    }

    if ( ! b )
        // All copied. Done.
        return;

    while ( b && (o = __get_object(b)) ) {
        // Duplicate object.
        void* p = &o->object;
        __hlt_bytes_object* no = (__hlt_bytes_object*)__hlt_bytes_new_object(o->type, &p, ctx);
        __add_chunk(dst, &no->b);
        dst = &no->b;
        b = b->next;
        start = b->start;
    }

    if ( ! b )
        // All copied now. Done.
        return;

    // Create a new bytes object, copy the rest in there, and append it.
    hlt_bytes_size len = __hlt_bytes_len(b);

    if ( len ) {
        hlt_bytes* ext = __hlt_bytes_new(0, len, 0);
        __add_chunk(dst, ext);
        __hlt_bytes_duplicate_into_helper(ext, b, start, ctx);
    }
}

static inline void __hlt_bytes_duplicate_into(hlt_bytes* dst, hlt_bytes* src, hlt_execution_context* ctx)
{
    __hlt_bytes_duplicate_into_helper(dst, src, src->start, ctx);
}

static inline void __hlt_bytes_duplicate_into_2(hlt_bytes* dst, hlt_bytes* src1, hlt_bytes* src2, hlt_execution_context* ctx)
{
    // We temporarily connect the two.
    hlt_bytes* t = __tail(src1, true);
    t->next = src2;
    __hlt_bytes_duplicate_into_helper(dst, src1, src1->start, ctx);
    t->next = 0;
}

#endif

void hlt_bytes_dtor(hlt_type_info* ti, hlt_bytes* b)
{
    b->start = b->end = 0;
    GC_CLEAR(b->next, hlt_bytes);

    __hlt_bytes_object* obj = __get_object(b);

    if ( obj ) {
        GC_DTOR_GENERIC(&obj->object, obj->type);
    }

    else if ( b->to_free )
        hlt_free(b->to_free);
}

void hlt_iterator_bytes_dtor(hlt_type_info* ti, hlt_iterator_bytes* p)
{
    if ( ! p->bytes )
        return;

    GC_CLEAR(p->bytes, hlt_bytes);
}

void hlt_iterator_bytes_cctor(hlt_type_info* ti, hlt_iterator_bytes* p)
{
    if ( ! p->bytes )
        return;

    GC_CCTOR(p->bytes, hlt_bytes)
}

hlt_bytes* hlt_bytes_new(hlt_exception** excpt, hlt_execution_context* ctx)
{
    return __hlt_bytes_new(0, 0, 0);
}

hlt_bytes* hlt_bytes_new_from_data(int8_t* data, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx)
{
    return __hlt_bytes_new_reuse(data, len);
}

hlt_bytes* hlt_bytes_new_from_data_copy(const int8_t* data, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx)
{
    return __hlt_bytes_new(data, len, 0);
}

void* hlt_bytes_clone_alloc(const hlt_type_info* ti, void* srcp, __hlt_clone_state* cstate, hlt_exception** excpt, hlt_execution_context* ctx)
{
    hlt_bytes* src = *(hlt_bytes**)srcp;

    __hlt_bytes_object *so = __get_object(src);

    if ( so )
        return __hlt_bytes_new_object(so->type, 0, ctx);
    else
        return __hlt_bytes_new(0, src->end - src->start, 0);
}

void hlt_bytes_clone_init(void* dstp, const hlt_type_info* ti, void* srcp, __hlt_clone_state* cstate, hlt_exception** excpt, hlt_execution_context* ctx)
{
    hlt_bytes* src = *(hlt_bytes**)srcp;
    hlt_bytes* dst = *(hlt_bytes**)dstp;

    assert(src && dst);

    dst->flags = src->flags;
    dst->offset = src->offset;

    int first = 1;

    for ( ; src; src = src->next ) {

        hlt_bytes* b = 0;

        __hlt_bytes_object *so = __get_object(src);

        if ( so ) {
            if ( first ) {
                // Already allocated.
                __hlt_bytes_object* no = (__hlt_bytes_object*) dst;
                memcpy(&no->object, &so->object, no->type->size);
                GC_CCTOR_GENERIC(&no->object, no->type);
                b = &no->b;
            }

            else {
                void* p = &so->object;
                __hlt_bytes_object* no = (__hlt_bytes_object*)__hlt_bytes_new_object(so->type, p, ctx);
                b = &no->b;
            }
        }

        else {
            if  ( first ) {
                // Already allocated.
                hlt_bytes_size n = (src->end - src->start);
                memcpy(dst->start, src->start, n);
                b = dst;
            }

            else {
                hlt_bytes_size n = (src->end - src->start);
                b = __hlt_bytes_new(src->start, n, 0);
            }
        }

        if ( ! first ) {
            __add_chunk(dst, b);
            dst = b;
        }

        else
            first = 0;

    }
}

// Returns the number of bytes stored.
hlt_bytes_size hlt_bytes_len(hlt_bytes* b, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return 0;
    }

    return __hlt_bytes_len(b);
}

// Returns true if empty.
int8_t hlt_bytes_empty(hlt_bytes* b, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return 0;
    }

    return __is_empty(b, false);
}

void __hlt_bytes_append_raw(hlt_bytes* b, int8_t* raw, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx, int8_t reuse)
{
    if ( ! len )
        return;

    if ( __is_frozen(b) ) {
        hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        return;
    }

    // TODO: Copy into available space if large enough.

    hlt_bytes* c;

    if ( reuse )
        c = __hlt_bytes_new_reuse(raw, len);
    else
        c = __hlt_bytes_new(raw, len, 0);

    __add_chunk(__tail(b, true), c);

    hlt_thread_mgr_unblock(&b->blockable, ctx);
}

// Appends one Bytes object to another.
void hlt_bytes_append(hlt_bytes* b, hlt_bytes* other, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return;
    }

    if ( __is_frozen(b) ) {
        hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        return;
    }

    hlt_bytes_size len = hlt_bytes_len(other, excpt, ctx);

    if ( ! len )
        return;

    // TODO: Copy into available space if large enough.

    hlt_bytes* dst = __hlt_bytes_new(0, len, 0);
    int8_t* p = dst->start;

    for ( ; other && ! __get_object(other); other = other->next ) {
        hlt_bytes_size n = other->end - other->start;
        memcpy(p, other->start, n);
        p += n;
    }

    __add_chunk(__tail(b, true), dst);

    hlt_thread_mgr_unblock(&b->blockable, ctx);
}

void hlt_bytes_append_raw(hlt_bytes* b, int8_t* raw, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return;
    }

    __hlt_bytes_append_raw(b, raw, len, excpt, ctx, 1);
}

void hlt_bytes_append_raw_copy(hlt_bytes* b, int8_t* raw, hlt_bytes_size len, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return;
    }

    __hlt_bytes_append_raw(b, raw, len, excpt, ctx, 0);
}

hlt_bytes* hlt_bytes_concat(hlt_bytes* b1, hlt_bytes* b2, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! (b1 && b2) ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return 0;
    }

    hlt_bytes_size len1 = hlt_bytes_len(b1, excpt, ctx);
    hlt_bytes_size len2 = hlt_bytes_len(b2, excpt, ctx);

    hlt_bytes* dst = __hlt_bytes_new(0, len1 + len2, 0);

    int8_t* p = dst->start;

    for ( ; b1 && ! __get_object(b1); b1 = b1->next ) {
        hlt_bytes_size n = b1->end - b1->start;
        memcpy(p, b1->start, n);
        p += n;
    }

    for ( ; b2 && ! __get_object(b2); b2 = b2->next ) {
        hlt_bytes_size n = b2->end - b2->start;
        memcpy(p, b2->start, n);
        p += n;
    }

    return dst;
}

// Doesn't ref the iterator.
void __hlt_bytes_find_byte(hlt_iterator_bytes* p, hlt_bytes* b, int8_t chr, hlt_exception** excpt, hlt_execution_context* ctx)
{
    for ( ; b && ! __get_object(b); b = b->next ) {
        int8_t* c = memchr(b->start, chr, b->end - b->start);

        if ( c ) {
            *p = __create_iterator(b, c);
            return;
        }
    }

    *p = GenericEndPos;
}

// Doesn't ref the iterator.
void __hlt_bytes_end(hlt_iterator_bytes* p, hlt_bytes* b, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        *p = GenericEndPos;
        return;
    }

    p->bytes = __tail(b, false);
    p->cur = p->bytes->end;
}

// Doesn't ref the iterator.
void __hlt_bytes_begin(hlt_iterator_bytes* p, hlt_bytes* b, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_empty(b, false) ) {
        __hlt_bytes_end(p, b, excpt, ctx);
        return;
    }

    p->bytes = b;
    p->cur = b->start;
    __normalize_iter(p);
}

// Doesn't ref the iterator.
void __hlt_bytes_find_byte_from(hlt_iterator_bytes* p, hlt_iterator_bytes i, int8_t chr, hlt_exception** excpt, hlt_execution_context* ctx)
{
    // First chunk.
    hlt_bytes* b = i.bytes;
    int8_t* c = memchr(i.cur, chr, b->end - i.cur);

    if ( ! c ) {
        // Subsequent chunks.
        for ( b = i.bytes->next; b && ! __get_object(b); b = b->next ) {
            int8_t* c = memchr(b->start, chr, b->end - b->start);

            if ( c )
                break;
        }
    }

    if ( c )
        *p = __create_iterator(b, c);
    else
        __hlt_bytes_end(p, i.bytes, excpt, ctx);
}

hlt_iterator_bytes hlt_bytes_find_byte(hlt_bytes* b, int8_t chr, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( ! b ) {
        hlt_set_exception(excpt, &hlt_exception_null_reference, 0);
        return GenericEndPos;
    }

    hlt_iterator_bytes p;
    __hlt_bytes_find_byte(&p, b, chr, excpt, ctx);
    GC_CCTOR(p, hlt_iterator_bytes);
    return p;
}

void __hlt_iterator_bytes_incr(hlt_iterator_bytes* p, hlt_exception** excpt, hlt_execution_context* ctx, int8_t adj_ref)
{
    if ( __is_end(*p) )
        // Fail silently.
        return;

    // Can we stay inside the same chunk?
    if ( p->cur + 1 < p->bytes->end ) {
        ++p->cur;
        return;
    }

    if ( ! p->bytes->next ) {
        // End reached.
        p->cur = p->bytes->end;
        return;
    }

    // Switch chunk.
    if ( adj_ref ) {
        GC_ASSIGN(p->bytes, p->bytes->next, hlt_bytes);
    }

    else
        p->bytes = p->bytes->next;

    p->cur = p->bytes->start;
}

void __hlt_iterator_bytes_incr_by(hlt_iterator_bytes* p, int64_t n, hlt_exception** excpt, hlt_execution_context* ctx, int8_t adj_ref)
{
    if ( __is_end(*p) )
        // Fail silently.
        return;

    while ( 1 ) {

        // Can we stay inside the same chunk?
        if ( p->cur + n < p->bytes->end ) {
            p->cur += n;
            return;
        }

        if ( ! p->bytes->next || __get_object(p->bytes) ) {
            // End reached.
            p->cur = p->bytes->end;
            return;
        }

        // Switch chunk.
        n -= (p->bytes->end - p->cur);

        if ( adj_ref ) {
            GC_ASSIGN(p->bytes, p->bytes->next, hlt_bytes);
        }

        else
            p->bytes = p->bytes->next;

        p->cur = p->bytes->start;
    }

    // Cannot reach.
    assert(false);
}

int8_t __hlt_iterator_bytes_deref(hlt_iterator_bytes p, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_end(p) ) {
        // Position is out range.
        hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        return 0;
    }

    return *p.cur;
}

int8_t __hlt_iterator_bytes_eq(hlt_iterator_bytes p1, hlt_iterator_bytes p2, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_end(p1) && __is_end(p2) )
        return 1;

    return p1.bytes == p2.bytes && p1.cur == p2.cur;
}

// Returns the number of bytes from p1 to p2 (not counting p2).
hlt_bytes_size __hlt_iterator_bytes_diff(hlt_iterator_bytes p1, hlt_iterator_bytes p2, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_end(p1) ) {
        if ( ! __is_end(p2) && ! (__at_object(p1) || __at_object(p2)) ) {
            // Invalid starting position.
            hlt_set_exception(excpt, &hlt_exception_value_error, 0);
        }

        return 0;
    }

    if ( __is_end(p2) )
        return hlt_bytes_len(p1.bytes, excpt, ctx) - (p1.cur - p1.bytes->start);

    return hlt_iterator_bytes_index(p2, excpt, ctx) - hlt_iterator_bytes_index(p1, excpt, ctx);
}

// Doesn't ref the iterator.
int8_t __hlt_bytes_find_bytes(hlt_iterator_bytes* p, hlt_bytes* b, hlt_bytes* other, hlt_exception** excpt, hlt_execution_context* ctx)
{
    if ( __is_empty(other, false) ) {
        // Empty pattern returns start position.
        __hlt_bytes_begin(p, b, excpt, ctx);
        return 1;
    }

    hlt_iterator_bytes begin;
    hlt_iterator_bytes end;
    __hlt_bytes_begin(&begin, other, excpt, ctx);
    __hlt_bytes_end(&end, other, excpt, ctx);


    int8_t chr = *begin.cur;

    hlt_iterator_bytes i;
    __hlt_bytes_begin(&i, b, excpt, ctx);

    while ( 1 ) {
        __hlt_bytes_find_byte_from(p, i, chr, excpt, ctx);

        if ( __is_end(*p) ) {
            // Not found.
            break;
        }
{'fraction_non_alphanumeric': 0.12936730629978277, 'fraction_numerical': 0.00832729905865315, 'mean_word_length': 2.46883830455259, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 460, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5583877', 'n_tokens_mistral': 16716, 'n_tokens_neox': 16161, 'n_words': 3849}