#include "line_index.h" #include #include #define LINE_IX_DYNAMIC_INIT 256 static size_t scan_line_end(const char *input, size_t len, size_t pos) { const char *hit = memchr(input + pos, '\n', len - pos); return hit ? (size_t)(hit - input) : len; } static size_t scan_skip_line(const char *input, size_t len, size_t pos) { pos = scan_line_end(input, len, pos); if (pos < len) pos++; return pos; } static int line_index_ensure(line_index_t *ix, uint32_t need_slots) { if (need_slots <= ix->cap) return 0; if (!ix->owned) { ix->valid = 0; return -1; } uint32_t cap = ix->cap ? ix->cap : LINE_IX_DYNAMIC_INIT; while (cap < need_slots) cap *= 2; uint32_t *p = (uint32_t *)realloc(ix->off, (size_t)cap * sizeof(uint32_t)); if (!p) { ix->valid = 0; return -1; } ix->off = p; ix->cap = cap; return 0; } int line_index_init_dynamic(line_index_t *ix, uint32_t initial_cap) { if (!ix) return -1; if (initial_cap < 2) initial_cap = LINE_IX_DYNAMIC_INIT; uint32_t *off = (uint32_t *)malloc((size_t)initial_cap * sizeof(uint32_t)); if (!off) return -1; ix->off = off; ix->cap = initial_cap; ix->n = 1; ix->off[0] = 0; ix->off[1] = 0; ix->cur = 0; ix->valid = 1; ix->owned = 1; return 0; } void line_index_free(line_index_t *ix) { if (!ix) return; if (ix->owned) { free(ix->off); ix->off = NULL; ix->owned = 0; ix->cap = 0; } } void line_index_build(const char *input, size_t len, line_index_t *ix) { ix->n = 1; ix->off[0] = 0; ix->cur = 0; ix->valid = 1; const char *start = input; const char *end = input + len; const char *hit; while ((hit = memchr(start, '\n', (size_t)(end - start))) != NULL) { if (ix->n + 1 >= ix->cap) { if (line_index_ensure(ix, ix->n + 2) != 0) return; } ix->off[ix->n++] = (uint32_t)(hit - input) + 1; start = hit + 1; } if (ix->n >= ix->cap) { if (line_index_ensure(ix, ix->n + 1) != 0) return; } ix->off[ix->n] = (uint32_t)len; } size_t line_index_extend(line_index_t *ix, const char *input, size_t old_len, size_t new_len) { if (!ix || !ix->valid || new_len <= old_len) return 0; size_t last_nl = 0; const char *start = input + old_len; const char *end = input + new_len; const char *hit; while ((hit = memchr(start, '\n', (size_t)(end - start))) != NULL) { if (ix->n + 1 >= ix->cap) { if (line_index_ensure(ix, ix->n + 2) != 0) return last_nl; } last_nl = (size_t)(hit - input) + 1; ix->off[ix->n++] = (uint32_t)last_nl; start = hit + 1; } if (ix->n >= ix->cap) { if (line_index_ensure(ix, ix->n + 1) != 0) return last_nl; } ix->off[ix->n] = (uint32_t)new_len; return last_nl; } static uint32_t line_no(const line_index_t *ix, size_t pos) { uint32_t lo = 0; uint32_t hi = ix->n; while (lo + 1 < hi) { uint32_t mid = (lo + hi) / 2; if (ix->off[mid] <= pos) lo = mid; else hi = mid; } return lo; } void line_index_seek(line_index_t *ix, size_t pos) { if (!ix || !ix->valid) return; ix->cur = line_no(ix, pos); } static uint32_t resolve_line(line_index_t *ix, size_t pos) { /* Sequential hot path: render walks lines in order. */ if (ix->cur < ix->n && ix->off[ix->cur] == (uint32_t)pos) return ix->cur; if (ix->cur + 1 < ix->n && ix->off[ix->cur + 1] == (uint32_t)pos) { ix->cur++; return ix->cur; } ix->cur = line_no(ix, pos); return ix->cur; } size_t line_end_ix(line_index_t *ix, const char *input, size_t len, size_t pos) { if (!ix || !ix->valid) return scan_line_end(input, len, pos); uint32_t k = resolve_line(ix, pos); size_t next = (size_t)ix->off[k + 1]; if (next > 0 && next <= len && input[next - 1] == '\n') return next - 1; return next <= len ? next : len; } size_t skip_line_ix(line_index_t *ix, const char *input, size_t len, size_t pos) { if (!ix || !ix->valid) return scan_skip_line(input, len, pos); uint32_t k = resolve_line(ix, pos); size_t next = (size_t)ix->off[k + 1]; if (next > len) return len; /* Advance cursor to the next line start (may equal n at EOF). */ ix->cur = k + 1; return next; }