From 0bfa9300dbbdae503a7a7f84fab493a275c9a91a Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Tue, 4 Aug 2026 07:48:26 +0100 Subject: [PATCH] Perfect - Make original buffer use a disk backed mapping to save memory. - rearrange api function headers for search etc. - fix bugs with iterators. - support uint64_t for all byte and line offsets to allow for much larger files to be opened. - and more minor bug fixes. --- include/pch.h | 3 + include/vase/buffer/append.h | 12 +- include/vase/buffer/buffer.h | 4 +- include/vase/buffer/original.h | 14 +- include/vase/constants.h | 4 +- include/vase/iterators/chunk.h | 15 +- include/vase/iterators/line.h | 9 +- include/vase/search.h | 21 -- include/vase/shard.h | 19 +- include/vase/vase.h | 69 +++-- src/main.cc | 6 +- src/vase/buffer/append.cc | 18 +- src/vase/buffer/original.cc | 31 ++- src/vase/iterators/chunk.cc | 68 +++-- src/vase/iterators/line.cc | 48 +++- src/vase/regex/api.cc | 151 ++++++++++ src/vase/{search.cc => regex/core.cc} | 72 ++--- src/vase/shard.cc | 58 ++-- src/vase/vase.cc | 386 +++++++++++--------------- 19 files changed, 612 insertions(+), 396 deletions(-) delete mode 100644 include/vase/search.h create mode 100644 src/vase/regex/api.cc rename src/vase/{search.cc => regex/core.cc} (77%) diff --git a/include/pch.h b/include/pch.h index ced4c6c..1db16a9 100644 --- a/include/pch.h +++ b/include/pch.h @@ -40,9 +40,12 @@ extern "C" { #include #include #include +#include #include #include #include +#include +#include #include #include #include diff --git a/include/vase/buffer/append.h b/include/vase/buffer/append.h index c37c233..0dc0d92 100644 --- a/include/vase/buffer/append.h +++ b/include/vase/buffer/append.h @@ -8,16 +8,16 @@ struct AppendBuffer : Buffer { using TChunk = char[APPEND_CHUNK_SIZE]; std::vector buf; TChunk *t_current; - uint32_t current_offset{0}; - uint32_t t_offset{0}; + uint64_t current_offset{0}; + uint64_t t_offset{0}; AppendBuffer(); ~AppendBuffer(); - uint32_t key(char c); - uint32_t append(const char *text, uint32_t length); + uint64_t append(char c); + uint64_t append(const char *text, uint64_t length); - const char *read(uint32_t pos, uint32_t *out_len); - inline uint32_t length(); + const char *read(uint64_t pos, uint64_t *out_len); + inline uint64_t length(); }; diff --git a/include/vase/buffer/buffer.h b/include/vase/buffer/buffer.h index 3058f59..e5ea308 100644 --- a/include/vase/buffer/buffer.h +++ b/include/vase/buffer/buffer.h @@ -3,7 +3,7 @@ #include "pch.h" struct Buffer { - virtual const char *read(uint32_t pos, uint32_t *out_len) = 0; - virtual inline uint32_t length() = 0; + virtual const char *read(uint64_t pos, uint64_t *out_len) = 0; + virtual inline uint64_t length() = 0; virtual ~Buffer() = default; }; diff --git a/include/vase/buffer/original.h b/include/vase/buffer/original.h index cd56d32..9b48165 100644 --- a/include/vase/buffer/original.h +++ b/include/vase/buffer/original.h @@ -4,14 +4,14 @@ #include "pch.h" struct OriginalBuffer : Buffer { - char *buf; - uint32_t len; - - // Add 2 modes here, normal and lazy, lazy copies the file to a safe location and uses mmap. - OriginalBuffer(std::string path); + const char *buf; + uint64_t len; + int fd = -1; + OriginalBuffer(); ~OriginalBuffer(); - const char *read(uint32_t pos, uint32_t *out_len); - uint32_t length(); + void initialize(); + const char *read(uint64_t pos, uint64_t *out_len); + uint64_t length(); }; diff --git a/include/vase/constants.h b/include/vase/constants.h index c4de939..28a8a87 100644 --- a/include/vase/constants.h +++ b/include/vase/constants.h @@ -2,8 +2,8 @@ #include "pch.h" -constexpr uint32_t APPEND_CHUNK_SIZE = 64 * 1024; -constexpr uint32_t PETAL_SIZE_MAX = 32 * 1024; +constexpr uint64_t APPEND_CHUNK_SIZE = 64 * 1024; +constexpr uint64_t PETAL_SIZE_MAX = 32 * 1024; static_assert( APPEND_CHUNK_SIZE > PETAL_SIZE_MAX, diff --git a/include/vase/iterators/chunk.h b/include/vase/iterators/chunk.h index 41d999f..72fd2d4 100644 --- a/include/vase/iterators/chunk.h +++ b/include/vase/iterators/chunk.h @@ -13,15 +13,18 @@ struct ChunkIterator { Shard *root = nullptr; std::vector stack; Petal *petal = nullptr; - uint32_t petal_offset = 0; - uint32_t global_offset = 0; + uint64_t petal_offset = 0; + uint64_t global_offset = 0; + uint64_t global_line = 0; + bool at_end = false; ChunkIterator(Shard *r, Direction dir); ~ChunkIterator(); - void seek_offset(uint32_t offset); - void seek_line(uint32_t offset); - bool next(const char **data, uint32_t *out_len); - uint32_t byte_offset(); + void seek_offset(uint64_t offset); + void seek_line(uint64_t offset); + bool next(const char **data, uint64_t *out_len); + uint64_t byte_offset(); + uint64_t line_offset(); }; diff --git a/include/vase/iterators/line.h b/include/vase/iterators/line.h index e2a68ed..33314c7 100644 --- a/include/vase/iterators/line.h +++ b/include/vase/iterators/line.h @@ -9,11 +9,14 @@ struct LineIterator { Direction dir; const char *chunk; - uint32_t len; + uint64_t len; + uint64_t offset = 0; + uint64_t chunk_offset = 0; + bool at_end = false; - LineIterator(Shard *r, uint32_t start_line, Direction dir); + LineIterator(Shard *r, uint64_t start_line, Direction dir); ~LineIterator() = default; bool next(std::string &line); - uint32_t byte_offset(); + uint64_t byte_offset(); }; diff --git a/include/vase/search.h b/include/vase/search.h deleted file mode 100644 index 1d6ec50..0000000 --- a/include/vase/search.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "pch.h" -#include "vase/shard.h" - -struct RegexGroup { - uint32_t start{UINT32_MAX}; - uint32_t end{UINT32_MAX}; -}; - -struct RegexMatch { - uint32_t start; - uint32_t end; - RegexGroup groups[9]{}; -}; - -std::vector regex_search( - Shard *root, std::string_view pattern_str, - uint32_t start_offset, uint32_t end_offset, - std::string_view options -); diff --git a/include/vase/shard.h b/include/vase/shard.h index 8b2c467..36bd98d 100644 --- a/include/vase/shard.h +++ b/include/vase/shard.h @@ -14,12 +14,12 @@ struct Shard { uint8_t height; - uint32_t length; - uint32_t lines; + uint64_t length; + uint64_t lines; - std::atomic_uint32_t refs; + std::atomic_uint64_t refs; - Shard(ShardKind kind, uint32_t length, uint32_t lines, uint8_t height) + Shard(ShardKind kind, uint64_t length, uint64_t lines, uint8_t height) : kind(kind), height(height), length(length), lines(lines), refs(1) {}; static void retain(Shard *n); @@ -46,18 +46,19 @@ struct Branch : Shard { struct Petal : Shard { Buffer *source; - uint32_t pos; + uint64_t pos; - Petal(uint32_t length, uint32_t lines, Buffer *source, uint32_t pos) + Petal(uint64_t length, uint64_t lines, Buffer *source, uint64_t pos) : Shard(ShardKind::Petal, length, lines, 1), source(source), pos(pos) {}; }; -Shard *create_shards(Buffer *o); +Shard *create_file_shards(std::string &path, OriginalBuffer *b); +Shard *create_swap_shards(std::string &path, OriginalBuffer *b); -std::pair split_shard(Shard *n, uint32_t offset); +std::pair split_shard(Shard *n, uint64_t offset); Shard *concat_shard(Shard *left, Shard *right); Shard *merge(Shard *a, Shard *b); Shard *merge_leaves(Shard *a, Shard *b); Shard *append_leaf(Shard *root, Shard *leaf); -Shard *build_balanced(Shard **pieces, uint32_t lo, uint32_t hi); +Shard *build_balanced(Shard **pieces, uint64_t lo, uint64_t hi); diff --git a/include/vase/vase.h b/include/vase/vase.h index e7ad451..2de228d 100644 --- a/include/vase/vase.h +++ b/include/vase/vase.h @@ -10,8 +10,8 @@ #include "utils/utils.h" struct Point { - uint32_t row; - uint32_t col; + uint64_t row; + uint64_t col; }; struct Range { @@ -19,30 +19,52 @@ struct Range { Point end; }; +struct RegexGroup { + uint64_t start{UINT32_MAX}; + uint64_t end{UINT32_MAX}; +}; + +struct RegexMatch { + uint64_t start; + uint64_t end; + RegexGroup groups[9]{}; +}; + +struct ReplacePart { + enum struct PartType { + FullMatch, + CaptureGroup, + Constant + } type; + + std::variant value; +}; + struct Vase { - OriginalBuffer original; - AppendBuffer append; + OriginalBuffer *original; + AppendBuffer *append; Shard *root; Vase(std::string path); ~Vase(); - uint32_t length(); + uint64_t length(); std::string to_string(); + std::string to_string(Range range); - Point resolve_column(uint32_t line, uint32_t column); + Point resolve_column(uint64_t line, uint64_t column); - LineIterator iterate(uint32_t line); + LineIterator iterate(uint64_t line); void insert(Point *point, char key); - void insert(Point *point, const char *data, uint32_t len); - void erase(Point *point, int64_t amount); + void insert(Point *point, const char *data, uint64_t len); + void erase(Point *point, uint64_t amount, Direction dir); void erase(Range range); - void replace(Range range, const char *data, uint32_t len); + void replace(Range range, const char *data, uint64_t len); - void move_clusters(Point *point, int64_t amount); - void move_lines(Point *point, int64_t amount); + void move_clusters(Point *point, uint64_t amount, Direction dir); + void move_lines(Point *point, uint64_t amount, Direction dir); void clamp(Point *point); void regex_search_replace( @@ -50,17 +72,30 @@ struct Vase { std::string_view replace, std::string_view options ); + std::vector regex_search( + std::string_view pattern, Range range, std::string_view options + ); + bool undo(); bool redo(); void snapshot(); - void prune_history(uint32_t n); + void prune_history(uint64_t n); + + bool save(std::string path); + bool save_swap(std::string path); + + uint64_t offset_of(Point point); + Point point_of(uint64_t offset); private: std::vector history; - uint32_t history_top; + uint64_t history_top; - uint32_t offset_of(Point point); - static void flatten(Shard *s, std::string &out); - void _insert(Point *point, const char *data, uint32_t len); + void _insert(Point *point, const char *data, uint64_t len); + + std::vector parse_replace(std::string_view s); + std::vector _regex_search( + std::string_view pattern, Range range, std::string_view options + ); }; diff --git a/src/main.cc b/src/main.cc index d9d85a3..62cb702 100644 --- a/src/main.cc +++ b/src/main.cc @@ -9,8 +9,12 @@ int main(int argc, char *argv[]) { Vase vase = Vase(std::string(argv[1])); + auto matches = vase.regex_search("hello", {{0, 0}, {vase.root->lines, UINT64_MAX}}, "g"); + std::cout << "\n" - << vase.to_string() + << (int)vase.length() << " bytes\n" + << (int)vase.root->lines << " lines\n" + << (int)matches.size() << " matches\n" << "\n"; return 0; diff --git a/src/vase/buffer/append.cc b/src/vase/buffer/append.cc index e798ce1..7b2fd81 100644 --- a/src/vase/buffer/append.cc +++ b/src/vase/buffer/append.cc @@ -10,7 +10,7 @@ AppendBuffer::~AppendBuffer() { free(p); } -uint32_t AppendBuffer::key(char c) { +uint64_t AppendBuffer::append(char c) { if (t_offset == APPEND_CHUNK_SIZE) { t_current = (TChunk *)malloc(sizeof(TChunk)); buf.push_back(t_current); @@ -20,15 +20,15 @@ uint32_t AppendBuffer::key(char c) { return current_offset++; } -uint32_t AppendBuffer::append(const char *text, uint32_t length) { - uint32_t start = current_offset; +uint64_t AppendBuffer::append(const char *text, uint64_t length) { + uint64_t start = current_offset; while (length > 0) { if (t_offset == APPEND_CHUNK_SIZE) { t_current = (TChunk *)malloc(sizeof(TChunk)); buf.push_back(t_current); t_offset = 0; } - uint32_t copy = std::min(length, APPEND_CHUNK_SIZE - t_offset); + uint64_t copy = std::min(length, APPEND_CHUNK_SIZE - t_offset); memcpy((*t_current) + t_offset, text, copy); t_offset += copy; current_offset += copy; @@ -38,18 +38,18 @@ uint32_t AppendBuffer::append(const char *text, uint32_t length) { return start; } -const char *AppendBuffer::read(uint32_t pos, uint32_t *out_len) { +const char *AppendBuffer::read(uint64_t pos, uint64_t *out_len) { if (pos >= current_offset) return nullptr; - uint32_t local_offset = pos % APPEND_CHUNK_SIZE; + uint64_t local_offset = pos % APPEND_CHUNK_SIZE; if (out_len) { - uint32_t remaining = current_offset - pos; - uint32_t until_chunk_end = APPEND_CHUNK_SIZE - local_offset; + uint64_t remaining = current_offset - pos; + uint64_t until_chunk_end = APPEND_CHUNK_SIZE - local_offset; *out_len = std::min(remaining, until_chunk_end); } return &(*buf[pos / APPEND_CHUNK_SIZE])[local_offset]; } -inline uint32_t AppendBuffer::length() { +inline uint64_t AppendBuffer::length() { return current_offset; } diff --git a/src/vase/buffer/original.cc b/src/vase/buffer/original.cc index 716c6a2..5997e1b 100644 --- a/src/vase/buffer/original.cc +++ b/src/vase/buffer/original.cc @@ -1,15 +1,36 @@ #include "vase/buffer/original.h" #include "io/file.h" -OriginalBuffer::OriginalBuffer(std::string path) { - read_file(path.c_str(), &buf, &len); +OriginalBuffer::OriginalBuffer() { + char tmp_path[] = "/tmp/tbuf.XXXXXX"; + fd = mkstemp(tmp_path); + if (fd == -1) + exit(1); + unlink(tmp_path); } OriginalBuffer::~OriginalBuffer() { - free(buf); + if (buf) + munmap((char *)buf, len); + if (fd != -1) + close(fd); } -const char *OriginalBuffer::read(uint32_t pos, uint32_t *out_len) { +void OriginalBuffer::initialize() { + struct stat st; + if (fstat(fd, &st) == -1) + throw std::runtime_error("fstat failed"); + len = (uint64_t)st.st_size; + buf = (const char *)mmap(nullptr, len, PROT_READ, MAP_PRIVATE, fd, 0); + if (buf == MAP_FAILED) { + buf = nullptr; + throw std::runtime_error("mmap failed"); + } + close(fd); + fd = -1; +} + +const char *OriginalBuffer::read(uint64_t pos, uint64_t *out_len) { if (pos >= len) return nullptr; if (out_len) @@ -17,6 +38,6 @@ const char *OriginalBuffer::read(uint32_t pos, uint32_t *out_len) { return buf + pos; } -uint32_t OriginalBuffer::length() { +uint64_t OriginalBuffer::length() { return len; } diff --git a/src/vase/iterators/chunk.cc b/src/vase/iterators/chunk.cc index 9db32e4..c855f86 100644 --- a/src/vase/iterators/chunk.cc +++ b/src/vase/iterators/chunk.cc @@ -11,15 +11,18 @@ ChunkIterator::~ChunkIterator() { Shard::release(root); } -void ChunkIterator::seek_offset(uint32_t offset) { +void ChunkIterator::seek_offset(uint64_t offset) { stack.clear(); petal = nullptr; petal_offset = 0; + global_offset = 0; + global_line = 0; + at_end = false; if (!root) return; if (offset >= root->length) offset = root->length - 1; - uint32_t target = offset; + uint64_t target = offset; Shard *curr = root; while (curr) { if (curr->kind == Shard::ShardKind::Petal) { @@ -29,7 +32,7 @@ void ChunkIterator::seek_offset(uint32_t offset) { return; } else { auto *b = (Branch *)curr; - uint32_t left_len = b->left->length; + uint64_t left_len = b->left->length; if (target < left_len) { if (dir == Direction::Forward) stack.push_back(b->right); @@ -39,6 +42,7 @@ void ChunkIterator::seek_offset(uint32_t offset) { if (dir == Direction::Backward) stack.push_back(b->left); global_offset += left_len; + global_line += b->left->lines; curr = b->right; } } @@ -46,31 +50,40 @@ void ChunkIterator::seek_offset(uint32_t offset) { std::unreachable(); } -void ChunkIterator::seek_line(uint32_t line) { +void ChunkIterator::seek_line(uint64_t line) { stack.clear(); petal = nullptr; petal_offset = 0; + global_offset = 0; + at_end = false; bool last_line = false; if (!root) return; - if (line > root->lines + 1) - line = root->lines + 1; - if (line == root->lines + 1) - last_line = true; + if (dir == Direction::Backward) { + if (line > root->lines + 1) + line = root->lines + 1; + if (line == root->lines + 1) + last_line = true; + } else { + if (line > root->lines) + line = root->lines; + if (line == root->lines) + last_line = true; + } Shard *curr = root; while (curr) { if (curr->kind == Shard::ShardKind::Petal) { petal = (Petal *)curr; - if (last_line) { + if (last_line && dir == Direction::Backward) { petal_offset = petal->length; global_offset += petal->length; } else { const char *text = nullptr; - uint32_t remaining = 0; - uint32_t offset = 0; + uint64_t remaining = 0; + uint64_t offset = 0; while (line) { if (!text) { - uint32_t got = 0; + uint64_t got = 0; text = petal->source->read(petal->pos + offset, &got); remaining = std::min(got, petal->length - offset); } @@ -87,13 +100,15 @@ void ChunkIterator::seek_line(uint32_t line) { } if (dir == Direction::Backward) --offset; + if (last_line && dir == Direction::Forward) + at_end = true; petal_offset = offset; global_offset += offset; } return; } else { auto *b = (Branch *)curr; - uint32_t left_lines = b->left->lines; + uint64_t left_lines = b->left->lines; if (line <= left_lines) { if (dir == Direction::Forward) stack.push_back(b->right); @@ -110,30 +125,41 @@ void ChunkIterator::seek_line(uint32_t line) { std::unreachable(); } -uint32_t ChunkIterator::byte_offset() { +uint64_t ChunkIterator::byte_offset() { return global_offset; } -bool ChunkIterator::next(const char **data, uint32_t *out_len) { +uint64_t ChunkIterator::line_offset() { + return global_line; +} + +bool ChunkIterator::next(const char **data, uint64_t *out_len) { if (dir == Direction::Forward) { while (true) { if (petal) { if (petal_offset == petal->length) { + if (at_end) { + *data = nullptr; + *out_len = 0; + at_end = false; + return true; + } petal = nullptr; continue; } - uint32_t remaining = petal->length - petal_offset; - uint32_t got = 0; + uint64_t remaining = petal->length - petal_offset; + uint64_t got = 0; const char *chunk = petal->source->read(petal->pos + petal_offset, &got); if (got == 0) { petal = nullptr; petal_offset = 0; return false; } - uint32_t take = std::min(got, remaining); + uint64_t take = std::min(got, remaining); *data = chunk; *out_len = take; petal_offset += take; + global_offset += take; return true; } if (stack.empty()) @@ -155,17 +181,19 @@ bool ChunkIterator::next(const char **data, uint32_t *out_len) { petal = nullptr; continue; } - uint32_t got = 0; + uint64_t got = 0; *data = petal->source->read(petal->pos, &got); if (petal_offset > got) { - uint32_t got2 = 0; + uint64_t got2 = 0; *data = petal->source->read(petal->pos + got, &got2); *out_len = std::min(got2, petal_offset - got); petal_offset = got; + global_offset -= *out_len; return true; } else { *out_len = std::min(got, petal_offset); petal_offset = 0; + global_offset -= *out_len; return true; } } diff --git a/src/vase/iterators/line.cc b/src/vase/iterators/line.cc index 17cad93..cb49aa6 100644 --- a/src/vase/iterators/line.cc +++ b/src/vase/iterators/line.cc @@ -1,55 +1,86 @@ #include "vase/iterators/line.h" -LineIterator::LineIterator(Shard *r, uint32_t start_line, Direction dir) +LineIterator::LineIterator(Shard *r, uint64_t start_line, Direction dir) : it(r, dir), dir(dir) { - it.seek_line(start_line + (dir == Direction::Backward)); - if (!it.next(&chunk, &len)) + int b = start_line == UINT64_MAX ? 0 : dir == Direction::Backward; + it.seek_line(start_line + b); + if (!it.next(&chunk, &len)) { chunk = nullptr; + return; + } + if (chunk == nullptr) + at_end = true; + if (dir == Direction::Forward) + chunk_offset = it.byte_offset() - len; + else + chunk_offset = it.byte_offset(); } -uint32_t LineIterator::byte_offset() { - return 0; +uint64_t LineIterator::byte_offset() { + return offset; } bool LineIterator::next(std::string &line) { line.clear(); + if (at_end) { + offset = chunk_offset; + at_end = false; + return true; + } if (!chunk) return false; if (dir == Direction::Forward) { + offset = chunk_offset; while (true) { const char *nl = (const char *)memchr(chunk, '\n', len); if (!nl) { if (len && chunk[len - 1] == '\r') --len; line.append(chunk, len); + chunk_offset += len; if (!it.next(&chunk, &len)) { chunk = nullptr; return true; } + if (chunk == nullptr) + return true; + chunk_offset = it.byte_offset() - len; continue; } const char *end = nl; if (end > chunk && *(end - 1) == '\r') --end; line.append(chunk, end); - uint32_t consumed = (nl - chunk) + 1; + uint64_t consumed = (nl - chunk) + 1; chunk += consumed; len -= consumed; + chunk_offset += consumed; return true; } } else { while (true) { +#if defined(__GLIBC__) || defined(__APPLE__) const char *nl = (const char *)memrchr(chunk, '\n', len); +#else + const char *nl = nullptr; + uint64_t i = len; + while (i--) { + if (chunk[i] == '\n') { + nl = chunk + i; + break; + } + } +#endif if (!nl) { - if (len && chunk[len - 1] == '\n') - --len; if (len && chunk[len - 1] == '\r') --len; line.insert(0, chunk, len); + chunk_offset -= len; if (!it.next(&chunk, &len)) { chunk = nullptr; return true; } + chunk_offset = it.byte_offset(); continue; } const char *end = chunk + len; @@ -59,6 +90,7 @@ bool LineIterator::next(std::string &line) { --line_end; line.insert(0, start, line_end - start); len = nl - chunk; + offset = chunk_offset + (start - chunk); return true; } } diff --git a/src/vase/regex/api.cc b/src/vase/regex/api.cc new file mode 100644 index 0000000..28e42ce --- /dev/null +++ b/src/vase/regex/api.cc @@ -0,0 +1,151 @@ +#include "vase/vase.h" + +std::vector Vase::parse_replace(std::string_view s) { + std::vector parts; + std::string constant; + auto flush_constant = [&]() { + if (!constant.empty()) { + uint64_t lines = 0; + uint64_t pos = append->append(constant.data(), (uint64_t)constant.size()); + parts.push_back( + ReplacePart{ + .type = ReplacePart::PartType::Constant, + .value = new Petal((uint64_t)constant.size(), lines, append, pos) + } + ); + constant.clear(); + } + }; + for (size_t i = 0; i < s.size(); ++i) { + char c = s[i]; + if (c == '\\' && i + 1 < s.size() && s[i + 1] == '$') { + constant.push_back('$'); + ++i; + continue; + } + if (c == '$' && i + 1 < s.size()) { + char next = s[i + 1]; + if (next == '0') { + flush_constant(); + parts.push_back( + ReplacePart{ + .type = ReplacePart::PartType::FullMatch, + .value = (uint8_t)0 + } + ); + ++i; + continue; + } + if (next >= '1' && next <= '9') { + flush_constant(); + parts.push_back( + ReplacePart{ + .type = ReplacePart::PartType::CaptureGroup, + .value = (uint8_t)(next - '0') + } + ); + ++i; + continue; + } + } + constant.push_back(c); + } + flush_constant(); + return parts; +} + +void Vase::regex_search_replace( + std::string_view pattern, Range range, + std::string_view replace, std::string_view options +) { + std::vector matches = _regex_search(pattern, range, options); + if (matches.empty()) + return; + + std::vector replace_parts = parse_replace(replace); + + std::vector pieces; + pieces.reserve(matches.size() * 2 + 1); + + Shard *remaining = root; + Shard::retain(remaining); + uint64_t cursor = 0; + + for (const RegexMatch &match : matches) { + uint64_t gap = match.start - cursor; + if (gap > 0) { + auto [keep, rest] = split_shard(remaining, gap); + Shard::release(remaining); + pieces.push_back(keep); + remaining = rest; + } + + auto [dropped, rest2] = split_shard(remaining, match.end - match.start); + Shard::release(remaining); + remaining = rest2; + + for (size_t i = 0; i < replace_parts.size(); ++i) { + const ReplacePart &part = replace_parts[i]; + switch (part.type) { + case ReplacePart::PartType::Constant: + Shard::retain(std::get(part.value)); + pieces.push_back(std::get(part.value)); + break; + case ReplacePart::PartType::FullMatch: + Shard::retain(dropped); + pieces.push_back(dropped); + break; + case ReplacePart::PartType::CaptureGroup: { + uint8_t idx = std::get(part.value); + if (idx <= 9) { + const RegexGroup &group = match.groups[idx - 1]; + if (group.start != UINT32_MAX) { + uint64_t ls = group.start - match.start; + uint64_t le = group.end - match.start; + auto [a, b] = split_shard(dropped, ls); + auto [g, c] = split_shard(b, le - ls); + Shard::release(a); + Shard::release(b); + Shard::release(c); + pieces.push_back(g); + } + } + break; + } + } + } + Shard::release(dropped); + cursor = match.end; + } + pieces.push_back(remaining); + + for (auto &part : replace_parts) + if (part.type == ReplacePart::PartType::Constant) + Shard::release(std::get(part.value)); + + std::vector compact; + compact.reserve(pieces.size()); + for (Shard *p : pieces) { + if (p && p->length > 0) + compact.push_back(p); + else if (p) + Shard::release(p); + } + + Shard *new_root = compact.empty() ? nullptr : build_balanced(compact.data(), 0, compact.size()); + Shard::release(root); + root = new_root; +} + +std::vector Vase::regex_search( + std::string_view pattern, Range range, std::string_view options +) { + std::vector matches = _regex_search(pattern, range, options); + if (matches.empty()) + return {}; + std::vector result; + result.reserve(matches.size()); + for (auto match : matches) + result.push_back({point_of(match.start), point_of(match.end)}); + return result; +} diff --git a/src/vase/search.cc b/src/vase/regex/core.cc similarity index 77% rename from src/vase/search.cc rename to src/vase/regex/core.cc index 710bd4e..0569593 100644 --- a/src/vase/search.cc +++ b/src/vase/regex/core.cc @@ -1,13 +1,10 @@ -#include "vase/search.h" -#include "vase/iterators/chunk.h" +#include "vase/vase.h" -std::vector regex_search( - Shard *root, std::string_view pattern_str, - uint32_t start_offset, uint32_t end_offset, - std::string_view options +std::vector Vase::_regex_search( + std::string_view pattern, Range range, std::string_view options ) { bool global = false; - uint32_t flags = PCRE2_MULTILINE; + uint64_t flags = PCRE2_MULTILINE; const char *dot = "(?:(?!\\n)\\X)"; @@ -43,12 +40,12 @@ std::vector regex_search( PCRE2_SIZE erroroffset; std::string out; - out.reserve(pattern_str.size() * 2); + out.reserve(pattern.size() * 2); bool escaped = false; bool in_class = false; bool in_quote = false; - for (size_t i = 0; i < pattern_str.size(); i++) { - char c = pattern_str[i]; + for (size_t i = 0; i < pattern.size(); i++) { + char c = pattern[i]; if (escaped) { out += c; escaped = false; @@ -56,8 +53,8 @@ std::vector regex_search( } if (c == '\\') { out += c; - if (i + 1 < pattern_str.size()) { - char next = pattern_str[i + 1]; + if (i + 1 < pattern.size()) { + char next = pattern[i + 1]; if (next == 'Q') in_quote = true; else if (next == 'E') @@ -104,40 +101,45 @@ std::vector regex_search( pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL); + uint64_t start_offset = offset_of(range.start); + uint64_t end_offset = offset_of(range.end); + + std::cout << (int)start_offset << ":" << (int)end_offset; + ChunkIterator it(root, Direction::Forward); it.seek_offset(start_offset); const char *data; - uint32_t length; + uint64_t length; char buf[2048]; - uint32_t global_offset = start_offset; - int64_t offset = -1; + uint64_t global_offset = start_offset; + uint64_t offset = UINT64_MAX; auto record_match = [&](int rc, PCRE2_SIZE *ovector) { - if (global_offset + (uint32_t)ovector[1] > end_offset || ovector[0] == ovector[1]) + if (global_offset + (uint64_t)ovector[1] > end_offset || ovector[0] == ovector[1]) return; RegexMatch match{ - .start = global_offset + (uint32_t)ovector[0], - .end = global_offset + (uint32_t)ovector[1] + .start = global_offset + (uint64_t)ovector[0], + .end = global_offset + (uint64_t)ovector[1] }; for (int i = 1; i < rc && i <= 9; ++i) { PCRE2_SIZE s = ovector[2 * i]; PCRE2_SIZE e = ovector[2 * i + 1]; if (s != PCRE2_UNSET) { - match.groups[i].start = global_offset + (uint32_t)s; - match.groups[i].end = global_offset + (uint32_t)e; + match.groups[i].start = global_offset + (uint64_t)s; + match.groups[i].end = global_offset + (uint64_t)e; } } results.push_back(std::move(match)); }; - auto skip_to_next_line = [&](const char *&data, uint32_t &length, int64_t &offset, uint32_t &global_offset) { + auto skip_to_next_line = [&](const char *&data, uint64_t &length, uint64_t &offset, uint64_t &global_offset) { while (true) { const char *p = (const char *)memchr(data + offset, '\n', length - offset); if (p) { - uint32_t nl = p - data; + uint64_t nl = p - data; offset = nl + 1; return; } @@ -150,7 +152,7 @@ std::vector regex_search( }; while (global_offset < end_offset) { - if (offset == -1) { + if (offset == UINT64_MAX) { bool more = it.next(&data, &length); if (!more) break; @@ -158,7 +160,7 @@ std::vector regex_search( } while (offset < length) { - if (offset == -1) + if (offset == UINT64_MAX) break; int rc = pcre2_match( @@ -172,15 +174,15 @@ std::vector regex_search( ); if (rc == PCRE2_ERROR_PARTIAL) { - uint32_t buflen = 0; + uint64_t buflen = 0; PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); - uint32_t partial_start = ovector[0]; - uint32_t partial_length = length - partial_start; + uint64_t partial_start = ovector[0]; + uint64_t partial_length = length - partial_start; if (partial_length >= 2048) { global_offset += offset; - offset = -1; + offset = UINT64_MAX; continue; } @@ -189,7 +191,7 @@ std::vector regex_search( memcpy(buf, data + partial_start, partial_length); buflen += partial_length; - offset = -1; + offset = UINT64_MAX; bool exhausted = false; while (true) { @@ -201,7 +203,7 @@ std::vector regex_search( exhausted = true; break; } - uint32_t l = std::min(length, 2048 - buflen); + uint64_t l = std::min(length, 2048 - buflen); memcpy(buf + buflen, data, l); buflen += l; @@ -235,7 +237,7 @@ std::vector regex_search( } if (exhausted) { - uint32_t search_from = 0; + uint64_t search_from = 0; while (search_from <= buflen) { int rc = pcre2_match( re, @@ -251,7 +253,7 @@ std::vector regex_search( PCRE2_SIZE *ov = pcre2_get_ovector_pointer(match_data); record_match(rc, ov); if (global) { - search_from = (ov[1] == ov[0]) ? (uint32_t)ov[1] + 1 : (uint32_t)ov[1]; + search_from = (ov[1] == ov[0]) ? (uint64_t)ov[1] + 1 : (uint64_t)ov[1]; } else { const void *p = memchr(buf + ov[1], '\n', buflen - ov[1]); if (!p) @@ -259,7 +261,7 @@ std::vector regex_search( search_from = (const char *)p - buf + 1; } } - offset = -1; + offset = UINT64_MAX; } continue; @@ -267,7 +269,7 @@ std::vector regex_search( PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); record_match(rc, ovector); if (global) { - if ((int64_t)ovector[1] == offset) + if (ovector[1] == offset) offset++; else offset = ovector[1]; @@ -285,7 +287,7 @@ std::vector regex_search( } global_offset += offset; - offset = -1; + offset = UINT64_MAX; } pcre2_match_data_free(match_data); diff --git a/src/vase/shard.cc b/src/vase/shard.cc index 8587dad..704ce7e 100644 --- a/src/vase/shard.cc +++ b/src/vase/shard.cc @@ -112,7 +112,7 @@ Shard *merge(Shard *a, Shard *b) { return balance(new Branch(a, b)); } -std::pair split_shard(Shard *n, uint32_t offset) { +std::pair split_shard(Shard *n, uint64_t offset) { if (!n) return {nullptr, nullptr}; if (offset == 0) { @@ -139,10 +139,10 @@ std::pair split_shard(Shard *n, uint32_t offset) { } } else { Petal *p = (Petal *)n; - uint32_t count[2]{0}; - uint32_t read_offset = 0; + uint64_t count[2]{0}; + uint64_t read_offset = 0; while (read_offset < p->length) { - uint32_t got = 0; + uint64_t got = 0; const char *c = p->source->read(p->pos + read_offset, &got); const char *end = c + std::min(got, p->length - read_offset); const char *cursor = c; @@ -152,14 +152,14 @@ std::pair split_shard(Shard *n, uint32_t offset) { cursor = end; break; } - uint32_t nl_pos = read_offset + (uint32_t)(nl - c); + uint64_t nl_pos = read_offset + (uint64_t)(nl - c); if (nl_pos < offset) count[0]++; else count[1]++; cursor = nl + 1; } - read_offset += (uint32_t)(cursor - c); + read_offset += (uint64_t)(cursor - c); } auto left = new Petal( offset, @@ -208,7 +208,7 @@ Shard *concat_shard(Shard *left, Shard *right) { return merge(left, right); } -Shard *build_balanced(Shard **pieces, uint32_t lo, uint32_t hi) { +Shard *build_balanced(Shard **pieces, uint64_t lo, uint64_t hi) { if (hi - lo == 1) return pieces[lo]; size_t mid = lo + (hi - lo) / 2; @@ -220,19 +220,34 @@ Shard *build_balanced(Shard **pieces, uint32_t lo, uint32_t hi) { return node; } -Shard *create_shards(Buffer *o) { +Shard *create_file_shards(std::string &path, OriginalBuffer *o) { + int dest_fd = o->fd; + if (dest_fd == -1) + return nullptr; + int src_fd = open(path.c_str(), O_RDONLY); + if (src_fd == -1) + return nullptr; + struct stat st; + if (fstat(src_fd, &st) == -1) + return nullptr; + + uint64_t total = (uint64_t)st.st_size; std::vector pieces; - uint32_t pos = 0; - const uint32_t total = o->length(); + uint64_t pos = 0; pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX); + char buf[PETAL_SIZE_MAX]; while (pos < total) { - uint32_t len = 0; - const char *data = o->read(pos, &len); - uint32_t take = std::min(len, PETAL_SIZE_MAX); - uint32_t lines = 0; - const char *p = data; - const char *end = data + take; + uint64_t want = std::min(PETAL_SIZE_MAX, total - pos); + ssize_t got = pread(src_fd, buf, want, pos); + if (got <= 0) { + close(src_fd); + return nullptr; + } + uint64_t take = (uint64_t)got; + uint64_t lines = 0; + const char *p = buf; + const char *end = p + take; while (p < end) { const void *nl = memchr(p, '\n', end - p); if (!nl) @@ -240,14 +255,23 @@ Shard *create_shards(Buffer *o) { lines++; p = (const char *)nl + 1; } + ssize_t written = write(dest_fd, buf, take); + if (written != (ssize_t)take) { + close(src_fd); + return nullptr; + } pieces.push_back(new Petal(take, lines, o, pos)); pos += take; } + close(src_fd); + if (pieces.empty()) return nullptr; + + o->initialize(); + if (pieces.size() == 1) return pieces[0]; - return build_balanced(pieces.data(), 0, pieces.size()); } diff --git a/src/vase/vase.cc b/src/vase/vase.cc index 3d422e0..9d04d35 100644 --- a/src/vase/vase.cc +++ b/src/vase/vase.cc @@ -1,11 +1,11 @@ #include "vase/vase.h" #include "utils/utils.h" #include "vase/iterators/line.h" -#include "vase/search.h" -Vase::Vase(std::string path) - : original(path), append(), - root(create_shards(&original)) { +Vase::Vase(std::string path) { + append = new AppendBuffer(); + original = new OriginalBuffer(); + root = create_file_shards(path, original); history.push_back(root); Shard::retain(root); history_top = 0; @@ -15,19 +15,42 @@ Vase::~Vase() { Shard::release(root); for (auto s : history) Shard::release(s); + delete original; + delete append; } -uint32_t Vase::length() { +uint64_t Vase::length() { return root->length; } std::string Vase::to_string() { std::string out; - flatten(root, out); + ChunkIterator it(root, Direction::Forward); + it.seek_offset(0); + const char *data; + uint64_t len; + while (it.next(&data, &len)) + out.append(data, len); return out; } -LineIterator Vase::iterate(uint32_t line) { +std::string Vase::to_string(Range range) { + std::string out; + ChunkIterator it(root, Direction::Forward); + uint64_t start = offset_of(range.start); + it.seek_offset(start); + const char *data; + uint64_t len; + uint64_t remaining = offset_of(range.end) - start; + while (remaining && it.next(&data, &len)) { + uint64_t n = std::min(len, remaining); + out.append(data, n); + remaining -= n; + } + return out; +} + +LineIterator Vase::iterate(uint64_t line) { return LineIterator(root, line, Direction::Forward); } @@ -63,21 +86,40 @@ void Vase::snapshot() { history_top++; } -void Vase::prune_history(uint32_t n) { +void Vase::prune_history(uint64_t n) { n = std::min(n, history_top); - for (uint32_t i = 0; i < n; ++i) + for (uint64_t i = 0; i < n; ++i) Shard::release(history[i]); history.erase(history.begin(), history.begin() + n); history_top -= n; } +bool Vase::save(std::string path) { + std::ofstream file(path, std::ios::binary); + if (!file) + return false; + ChunkIterator it(root, Direction::Forward); + it.seek_offset(0); + const char *data; + uint64_t len; + while (it.next(&data, &len)) { + file.write(data, len); + if (!file) + return false; + } + return true; +} + +bool Vase::save_swap(std::string path) { +} + void Vase::insert(Point *point, char key) { if (key == '\n') *point = {point->row + 1, 0}; else point->col++; - uint32_t pos = append.key(key); - Shard *inserted = new Petal(1, key == '\n', &append, pos); + uint64_t pos = append->append(key); + Shard *inserted = new Petal(1, key == '\n', append, pos); auto [left, right] = split_shard(root, offset_of(*point)); Shard *left2 = append_leaf(left, inserted); Shard *new_root = concat_shard(left2, right); @@ -89,21 +131,21 @@ void Vase::insert(Point *point, char key) { root = new_root; } -void Vase::insert(Point *point, const char *data, uint32_t len) { +void Vase::insert(Point *point, const char *data, uint64_t len) { while (len) { - uint32_t chunk_size = std::min(len, PETAL_SIZE_MAX); + uint64_t chunk_size = std::min(len, PETAL_SIZE_MAX); _insert(point, data, chunk_size); len -= chunk_size; data += chunk_size; } } -void Vase::_insert(Point *point, const char *data, uint32_t len) { +void Vase::_insert(Point *point, const char *data, uint64_t len) { if (len == 0) return; - uint32_t offset = offset_of(*point); - uint32_t pos = append.append(data, len); - uint32_t lines = 0; + uint64_t offset = offset_of(*point); + uint64_t pos = append->append(data, len); + uint64_t lines = 0; const char *start = data; const char *last_line = start; const char *end = start + len; @@ -111,10 +153,10 @@ void Vase::_insert(Point *point, const char *data, uint32_t len) { ++lines; last_line = ++data; } - uint32_t col = 0; - uint32_t remaining = end - last_line; + uint64_t col = 0; + uint64_t remaining = end - last_line; while (remaining) { - uint32_t n = grapheme_next_character_break_utf8(last_line, remaining); + uint64_t n = grapheme_next_character_break_utf8(last_line, remaining); last_line += n; remaining -= n; ++col; @@ -125,7 +167,7 @@ void Vase::_insert(Point *point, const char *data, uint32_t len) { } else { point->col += col; } - Shard *inserted = new Petal(len, lines, &append, pos); + Shard *inserted = new Petal(len, lines, append, pos); auto [left, right] = split_shard(root, offset); Shard *left2 = append_leaf(left, inserted); Shard *new_root = concat_shard(left2, right); @@ -137,20 +179,20 @@ void Vase::_insert(Point *point, const char *data, uint32_t len) { root = new_root; } -void Vase::erase(Point *point, int64_t amount) { +void Vase::erase(Point *point, uint64_t amount, Direction dir) { if (amount == 0) return; Point start = *point; Point end = *point; - if (amount < 0) - move_clusters(&start, amount); + if (dir == Direction::Forward) + move_clusters(&end, amount, Direction::Forward); else - move_clusters(&end, amount); - uint32_t start_offset = offset_of(start); - uint32_t end_offset = offset_of(end); + move_clusters(&start, amount, Direction::Backward); + uint64_t start_offset = offset_of(start); + uint64_t end_offset = offset_of(end); if (start_offset > end_offset) std::swap(start_offset, end_offset); - uint32_t count = end_offset - start_offset; + uint64_t count = end_offset - start_offset; auto [a, b] = split_shard(root, start_offset); auto [d, c] = split_shard(b, count); Shard *new_root = concat_shard(a, c); @@ -160,16 +202,16 @@ void Vase::erase(Point *point, int64_t amount) { Shard::release(d); Shard::release(root); root = new_root; - if (amount < 0) + if (dir == Direction::Backward) *point = start; } void Vase::erase(Range range) { Point start = range.start; Point end = range.end; - uint32_t start_offset = offset_of(start); - uint32_t end_offset = offset_of(end); - uint32_t count = end_offset - start_offset; + uint64_t start_offset = offset_of(start); + uint64_t end_offset = offset_of(end); + uint64_t count = end_offset - start_offset; auto [a, b] = split_shard(root, start_offset); auto [d, c] = split_shard(b, count); Shard *new_root = concat_shard(a, c); @@ -181,40 +223,21 @@ void Vase::erase(Range range) { root = new_root; } -void Vase::replace(Range range, const char *data, uint32_t len) { +void Vase::replace(Range range, const char *data, uint64_t len) { erase(range); insert(&range.start, data, len); } -void Vase::flatten(Shard *s, std::string &out) { - if (s->kind == Shard::ShardKind::Petal) { - auto *p = (Petal *)s; - uint32_t remaining = p->length; - uint32_t pos = p->pos; - while (remaining) { - uint32_t got; - const char *data = p->source->read(pos, &got); - uint32_t take = std::min(got, remaining); - out.append(data, take); - remaining -= take; - pos += take; - } - } else { - auto *b = (Branch *)s; - flatten(b->left, out); - flatten(b->right, out); - } -} - -uint32_t Vase::offset_of(Point point) { +uint64_t Vase::offset_of(Point point) { LineIterator it(root, point.row, Direction::Forward); + std::string line; - uint32_t offset = 0; + uint64_t offset = 0; if (it.next(line)) { const char *ptr = line.data(); - uint32_t remaining = line.length(); + uint64_t remaining = line.length(); while (point.col && remaining) { - uint32_t next_len = grapheme_next_character_break_utf8(ptr, remaining); + uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining); remaining -= next_len; ptr += next_len; offset += next_len; @@ -224,29 +247,78 @@ uint32_t Vase::offset_of(Point point) { return it.byte_offset() + offset; } -void Vase::move_clusters(Point *point, int64_t amount) { +Point Vase::point_of(uint64_t offset) { + ChunkIterator it(root, Direction::Backward); + it.seek_offset(offset); + Point p; + p.row = it.line_offset(); + std::string line; + const char *chunk; + uint64_t len = 0; + if (!it.next(&chunk, &len)) + return p; + while (true) { +#if defined(__GLIBC__) || defined(__APPLE__) + const char *nl = (const char *)memrchr(chunk, '\n', len); +#else + const char *nl = nullptr; + uint64_t i = len; + while (i--) { + if (chunk[i] == '\n') { + nl = chunk + i; + break; + } + } +#endif + if (!nl) { + if (len && chunk[len - 1] == '\r') + --len; + line.insert(0, chunk, len); + if (!it.next(&chunk, &len)) + break; + continue; + } + const char *end = chunk + len; + const char *start = nl + 1; + const char *line_end = end; + if (line_end > start && *(line_end - 1) == '\r') + --line_end; + line.insert(0, start, line_end - start); + break; + } + const char *ptr = line.data(); + uint64_t remaining = line.length(); + while (remaining) { + uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining); + remaining -= next_len; + ptr += next_len; + p.col++; + } + return p; +} + +void Vase::move_clusters(Point *point, uint64_t amount, Direction dir) { if (amount == 0) return; - if (amount < 0) { - amount = -amount; + if (dir == Direction::Backward) { LineIterator it(root, point->row, Direction::Backward); - while (amount > 0) { + while (amount) { std::string line; if (!it.next(line)) return; - std::vector clusters; + std::vector clusters; const char *ptr = line.data(); - uint32_t remaining = line.size(); - uint32_t byte = 0; + uint64_t remaining = line.size(); + uint64_t byte = 0; while (remaining) { clusters.push_back(byte); - uint32_t len = + uint64_t len = grapheme_next_character_break_utf8(ptr, remaining); ptr += len; remaining -= len; byte += len; } - while (amount > 0 && point->col > 0) { + while (amount && point->col) { point->col--; amount--; } @@ -260,29 +332,29 @@ void Vase::move_clusters(Point *point, int64_t amount) { } } else { LineIterator it(root, point->row, Direction::Forward); - while (amount > 0) { + while (amount) { std::string line; if (!it.next(line)) return; - if (point->col > 0 || amount > 0) { + if (point->col || amount) { const char *ptr = line.data(); - uint32_t remaining = line.size(); - uint32_t col = 0; + uint64_t remaining = line.size(); + uint64_t col = 0; while (col < point->col && remaining) { - uint32_t len = grapheme_next_character_break_utf8(ptr, remaining); + uint64_t len = grapheme_next_character_break_utf8(ptr, remaining); ptr += len; remaining -= len; col++; } - while (amount > 0 && remaining) { - uint32_t len = grapheme_next_character_break_utf8(ptr, remaining); + while (amount && remaining) { + uint64_t len = grapheme_next_character_break_utf8(ptr, remaining); ptr += len; remaining -= len; point->col++; amount--; } } - if (amount > 0) { + if (amount) { point->row++; point->col = 0; amount--; @@ -300,11 +372,11 @@ void Vase::clamp(Point *point) { point->col = 0; return; } - uint32_t clusters = 0; + uint64_t clusters = 0; const char *ptr = line.data(); - uint32_t remaining = line.size(); + uint64_t remaining = line.size(); while (remaining) { - uint32_t len = grapheme_next_character_break_utf8(ptr, remaining); + uint64_t len = grapheme_next_character_break_utf8(ptr, remaining); ptr += len; remaining -= len; clusters++; @@ -313,156 +385,14 @@ void Vase::clamp(Point *point) { point->col = clusters; } -void Vase::move_lines(Point *point, int64_t amount) { - if (point->row + amount < 0) - return; - point->row += amount; +void Vase::move_lines(Point *point, uint64_t amount, Direction dir) { + if (dir == Direction::Forward) { + point->row += amount; + } else { + if (amount > point->row) + point->row = 0; + else + point->row -= amount; + } clamp(point); } - -struct ReplacePart { - enum struct PartType { - FullMatch, - CaptureGroup, - Constant - } type; - - std::variant value; -}; - -std::vector parse_replace(AppendBuffer &buf, std::string_view s) { - std::vector parts; - std::string constant; - auto flush_constant = [&]() { - if (!constant.empty()) { - uint32_t lines = 0; - uint32_t pos = buf.append(constant.data(), (uint32_t)constant.size()); - parts.push_back( - ReplacePart{ - .type = ReplacePart::PartType::Constant, - .value = new Petal((uint32_t)constant.size(), lines, &buf, pos) - } - ); - constant.clear(); - } - }; - for (size_t i = 0; i < s.size(); ++i) { - char c = s[i]; - if (c == '\\' && i + 1 < s.size() && s[i + 1] == '$') { - constant.push_back('$'); - ++i; - continue; - } - if (c == '$' && i + 1 < s.size()) { - char next = s[i + 1]; - if (next == '0') { - flush_constant(); - parts.push_back( - ReplacePart{ - .type = ReplacePart::PartType::FullMatch, - .value = (uint8_t)0 - } - ); - ++i; - continue; - } - if (next >= '1' && next <= '9') { - flush_constant(); - parts.push_back( - ReplacePart{ - .type = ReplacePart::PartType::CaptureGroup, - .value = (uint8_t)(next - '0') - } - ); - ++i; - continue; - } - } - constant.push_back(c); - } - flush_constant(); - return parts; -} - -void Vase::regex_search_replace( - std::string_view pattern, Range range, - std::string_view replace, std::string_view options -) { - std::vector matches = regex_search(root, pattern, offset_of(range.start), offset_of(range.end), options); - if (matches.empty()) - return; - - std::vector replace_parts = parse_replace(append, replace); - - std::vector pieces; - pieces.reserve(matches.size() * 2 + 1); - - Shard *remaining = root; - Shard::retain(remaining); - uint32_t cursor = 0; - - for (const RegexMatch &match : matches) { - uint32_t gap = match.start - cursor; - if (gap > 0) { - auto [keep, rest] = split_shard(remaining, gap); - Shard::release(remaining); - pieces.push_back(keep); - remaining = rest; - } - - auto [dropped, rest2] = split_shard(remaining, match.end - match.start); - Shard::release(remaining); - remaining = rest2; - - for (size_t i = 0; i < replace_parts.size(); ++i) { - const ReplacePart &part = replace_parts[i]; - switch (part.type) { - case ReplacePart::PartType::Constant: - Shard::retain(std::get(part.value)); - pieces.push_back(std::get(part.value)); - break; - case ReplacePart::PartType::FullMatch: - Shard::retain(dropped); - pieces.push_back(dropped); - break; - case ReplacePart::PartType::CaptureGroup: { - uint8_t idx = std::get(part.value); - if (idx <= 9) { - const RegexGroup &group = match.groups[idx - 1]; - if (group.start != UINT32_MAX) { - uint32_t ls = group.start - match.start; - uint32_t le = group.end - match.start; - auto [a, b] = split_shard(dropped, ls); - auto [g, c] = split_shard(b, le - ls); - Shard::release(a); - Shard::release(b); - Shard::release(c); - pieces.push_back(g); - } - } - break; - } - } - } - Shard::release(dropped); - cursor = match.end; - } - pieces.push_back(remaining); - - for (auto &part : replace_parts) - if (part.type == ReplacePart::PartType::Constant) - Shard::release(std::get(part.value)); - - std::vector compact; - compact.reserve(pieces.size()); - for (Shard *p : pieces) { - if (p && p->length > 0) - compact.push_back(p); - else if (p) - Shard::release(p); - } - - Shard *new_root = compact.empty() ? nullptr : build_balanced(compact.data(), 0, compact.size()); - Shard::release(root); - root = new_root; -}