From 9c84118e6342d688c0c424ea466d8eaba2217647 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Tue, 28 Jul 2026 18:01:48 +0100 Subject: [PATCH] Add a line content iterator. --- include/vase/buffer/append.h | 5 ++ include/vase/buffer/buffer.h | 3 +- include/vase/buffer/original.h | 2 + include/vase/line_iter.h | 26 +++++++++ include/vase/vase.h | 1 + src/main.cc | 22 +++++--- src/vase/buffer/append.cc | 19 +++++++ src/vase/buffer/original.cc | 12 +++++ src/vase/line_iter.cc | 98 ++++++++++++++++++++++++++++++++++ 9 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 include/vase/line_iter.h create mode 100644 src/vase/line_iter.cc diff --git a/include/vase/buffer/append.h b/include/vase/buffer/append.h index 2d16d45..5e1594e 100644 --- a/include/vase/buffer/append.h +++ b/include/vase/buffer/append.h @@ -27,7 +27,12 @@ struct AppendBuffer : Buffer { const char *read(uint32_t pos, uint32_t *out_len); uint32_t count_lines(uint32_t pos, uint32_t length); + uint32_t next_newline(uint32_t pos); + uint32_t nth_newline(uint32_t pos, uint32_t n); + private: + inline uint32_t newline_value_at(uint32_t flat_index); + inline void new_text_chunk(); inline void new_line_chunk(); diff --git a/include/vase/buffer/buffer.h b/include/vase/buffer/buffer.h index 57ef4f6..707d1a1 100644 --- a/include/vase/buffer/buffer.h +++ b/include/vase/buffer/buffer.h @@ -5,6 +5,7 @@ struct Buffer { virtual const char *read(uint32_t pos, uint32_t *out_len) = 0; virtual uint32_t count_lines(uint32_t pos, uint32_t length) = 0; - + virtual uint32_t next_newline(uint32_t pos) = 0; + virtual uint32_t nth_newline(uint32_t pos, uint32_t n) = 0; virtual ~Buffer() = default; }; diff --git a/include/vase/buffer/original.h b/include/vase/buffer/original.h index da55d86..83ac868 100644 --- a/include/vase/buffer/original.h +++ b/include/vase/buffer/original.h @@ -27,4 +27,6 @@ struct OriginalBuffer : Buffer { const char *read(uint32_t pos, uint32_t *out_len); uint32_t count_lines(uint32_t pos, uint32_t length); + uint32_t next_newline(uint32_t pos); + uint32_t nth_newline(uint32_t pos, uint32_t n); }; diff --git a/include/vase/line_iter.h b/include/vase/line_iter.h new file mode 100644 index 0000000..a7fbaa4 --- /dev/null +++ b/include/vase/line_iter.h @@ -0,0 +1,26 @@ +#pragma once +#include "pch.h" +#include "shard.h" + +struct LineIterator { + Shard *root = nullptr; + std::vector stack; + Petal *petal = nullptr; + uint32_t petal_offset = 0; + + LineIterator(Shard *r, uint32_t start_line); + + ~LineIterator(); + + LineIterator(const LineIterator &) = delete; + LineIterator &operator=(const LineIterator &) = delete; + LineIterator(LineIterator &&) = default; + LineIterator &operator=(LineIterator &&) = default; + + void seek_line(uint32_t line_index); + bool next(std::string &line); + +private: + void descend(Shard *s); + bool advance_petal(); +}; diff --git a/include/vase/vase.h b/include/vase/vase.h index 23534c0..c8299fa 100644 --- a/include/vase/vase.h +++ b/include/vase/vase.h @@ -2,6 +2,7 @@ #include "buffer/append.h" #include "buffer/original.h" +#include "line_iter.h" #include "pch.h" #include "shard.h" diff --git a/src/main.cc b/src/main.cc index 8b6df51..a215278 100644 --- a/src/main.cc +++ b/src/main.cc @@ -4,18 +4,18 @@ #include "vase/vase.h" int main() { - const char *text_o = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n"; + const char *text_o = + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr\n" + "st\n" + "uv\n" + "wx\n" + "yzabcdefghijklmnopqrstuvwxyz\n"; + uint32_t len = strlen(text_o); char *text = strdup(text_o); Vase vase = Vase(text, len); - std::cout << vase.to_string() << "\n"; - - print_shard(vase.root); - - std::cout << "\n->\n\n"; - vase.insert(14, "gr\ntt", 5); vase.insert(58, "gr\ntt", 5); vase.insert(100, "gr\ntt", 5); @@ -28,7 +28,13 @@ int main() { print_shard(vase.root); - std::cout << "\n" + LineIterator it(vase.root, 3); + std::string line; + while (it.next(line)) { + std::cout << line << "\n"; + } + + std::cout << "\n\n" << vase.to_string(); return 0; diff --git a/src/vase/buffer/append.cc b/src/vase/buffer/append.cc index 03874aa..9926b05 100644 --- a/src/vase/buffer/append.cc +++ b/src/vase/buffer/append.cc @@ -108,6 +108,25 @@ uint32_t AppendBuffer::find_newline(uint32_t target, uint32_t low, uint32_t high return chunk * CHUNK_SIZE + l; } +inline uint32_t AppendBuffer::newline_value_at(uint32_t flat_index) { + uint32_t chunk = flat_index / CHUNK_SIZE; + uint32_t off = flat_index % CHUNK_SIZE; + return (*newlines[chunk])[off]; +} + +uint32_t AppendBuffer::next_newline(uint32_t pos) { + uint32_t total = CHUNK_SIZE * (uint32_t)(newlines.size() - 1) + l_offset; + uint32_t idx = find_newline(pos, 0, total); + return idx >= total ? UINT32_MAX : newline_value_at(idx); +} + +uint32_t AppendBuffer::nth_newline(uint32_t pos, uint32_t n) { + uint32_t total = CHUNK_SIZE * (uint32_t)(newlines.size() - 1) + l_offset; + uint32_t idx0 = find_newline(pos, 0, total); + uint32_t idx = idx0 + (n - 1); + return idx >= total ? UINT32_MAX : newline_value_at(idx); +} + uint32_t AppendBuffer::count_lines(uint32_t pos, uint32_t length) { if (newlines.empty()) return 0; diff --git a/src/vase/buffer/original.cc b/src/vase/buffer/original.cc index c193300..7beb704 100644 --- a/src/vase/buffer/original.cc +++ b/src/vase/buffer/original.cc @@ -22,3 +22,15 @@ uint32_t OriginalBuffer::count_lines(uint32_t pos, uint32_t length) { return (uint32_t)(end - start); } + +uint32_t OriginalBuffer::next_newline(uint32_t pos) { + auto it = std::lower_bound(newlines.begin(), newlines.end(), pos); + return it == newlines.end() ? UINT32_MAX : *it; +} + +uint32_t OriginalBuffer::nth_newline(uint32_t pos, uint32_t n) { + auto it = std::lower_bound(newlines.begin(), newlines.end(), pos); + if (uint32_t(newlines.end() - it) < n) + return UINT32_MAX; + return *(it + (n - 1)); +} diff --git a/src/vase/line_iter.cc b/src/vase/line_iter.cc new file mode 100644 index 0000000..6844b88 --- /dev/null +++ b/src/vase/line_iter.cc @@ -0,0 +1,98 @@ +#include "vase/line_iter.h" + +LineIterator::LineIterator(Shard *r, uint32_t start_line) : root(r) { + if (!root) + return; + Shard::retain(root); + seek_line(start_line); +} + +LineIterator::~LineIterator() { + Shard::release(root); +} + +void LineIterator::descend(Shard *s) { + while (s->kind == Shard::ShardKind::Branch) { + auto *b = static_cast(s); + stack.push_back(b); + s = b->left; + } + petal = static_cast(s); + petal_offset = 0; +} + +bool LineIterator::advance_petal() { + while (!stack.empty()) { + Branch *top = stack.back(); + stack.pop_back(); + descend(top->right); + if (petal->length > 0) + return true; + } + petal = nullptr; + return false; +} + +void LineIterator::seek_line(uint32_t line_index) { + stack.clear(); + if (line_index == 0) { + descend(root); + return; + } + uint32_t nth = line_index; // we want the position right after the nth '\n' (1-indexed) + Shard *s = root; + while (s->kind == Shard::ShardKind::Branch) { + auto *b = static_cast(s); + if (nth <= b->left->lines) { + stack.push_back(b); // still need to visit b->right later + s = b->left; + } else { + nth -= b->left->lines; // left fully skipped, don't revisit it + s = b->right; + } + } + petal = static_cast(s); + uint32_t nl_pos = petal->source->nth_newline(petal->pos, nth); + petal_offset = (nl_pos - petal->pos) + 1; + if (petal_offset >= petal->length) + advance_petal(); +} + +bool LineIterator::next(std::string &line) { + line.clear(); + if (!petal) + return false; + + bool produced = false; + while (petal) { + produced = true; + uint32_t abs_pos = petal->pos + petal_offset; + uint32_t petal_end = petal->pos + petal->length; + + uint32_t nl = petal->source->next_newline(abs_pos); + uint32_t stop = (nl != UINT32_MAX && nl < petal_end) ? nl : petal_end; + + uint32_t remaining = stop - abs_pos; + uint32_t cur = abs_pos; + while (remaining) { + uint32_t got; + const char *data = petal->source->read(cur, &got); + uint32_t take = std::min(got, remaining); + line.append(data, take); + cur += take; + remaining -= take; + } + petal_offset = cur - petal->pos; + + if (stop == nl) { + petal_offset += 1; + if (petal_offset >= petal->length) + advance_petal(); + return true; + } + + if (!advance_petal()) + break; + } + return produced; +}