diff --git a/include/pch.h b/include/pch.h index 7bfb866..cc8bc55 100644 --- a/include/pch.h +++ b/include/pch.h @@ -1,7 +1,6 @@ #pragma once #define PCRE2_CODE_UNIT_WIDTH 8 -#define PCRE_WORKSPACE_SIZE 512 #include #include diff --git a/include/vase/buffer/original.h b/include/vase/buffer/original.h index 83ac868..8e08dec 100644 --- a/include/vase/buffer/original.h +++ b/include/vase/buffer/original.h @@ -9,21 +9,9 @@ struct OriginalBuffer : Buffer { std::vector newlines; // TODO: replace with file io or even lazy loaded file io for large files/logs etc. later. - OriginalBuffer(char *buf, uint32_t length) : buf(buf), length(length) { - const char *p = buf; - const char *end = buf + length; - while (p < end) { - p = (const char *)memchr(p, '\n', end - p); - if (!p) - break; - newlines.push_back(uint32_t(p - buf)); - p++; - } - } + OriginalBuffer(char *buf, uint32_t length); - ~OriginalBuffer() { - free(buf); - } + ~OriginalBuffer(); const char *read(uint32_t pos, uint32_t *out_len); uint32_t count_lines(uint32_t pos, uint32_t length); diff --git a/include/vase/iterators/chunk.h b/include/vase/iterators/chunk.h new file mode 100644 index 0000000..83901ee --- /dev/null +++ b/include/vase/iterators/chunk.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../shard.h" +#include "pch.h" + +struct ChunkIterator { + Shard *root = nullptr; + std::vector stack; + Petal *petal = nullptr; + uint32_t petal_offset = 0; + + ChunkIterator(Shard *r, uint32_t offset = 0); + + ~ChunkIterator(); + + void seek(uint32_t offset); + bool next(const char **data, uint32_t *out_len); +}; diff --git a/include/vase/iterators/line.h b/include/vase/iterators/line.h new file mode 100644 index 0000000..f042c84 --- /dev/null +++ b/include/vase/iterators/line.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../shard.h" +#include "chunk.h" +#include "pch.h" + +struct LineIterator { + uint32_t offset; + ChunkIterator it; + + const char *cursor = nullptr; + uint32_t remaining = 0; + bool eof = false; + + LineIterator(Shard *r, uint32_t start_line); + + ~LineIterator(); + + bool next(std::string &line); +}; diff --git a/include/vase/line_iter.h b/include/vase/line_iter.h deleted file mode 100644 index a7fbaa4..0000000 --- a/include/vase/line_iter.h +++ /dev/null @@ -1,26 +0,0 @@ -#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/search.h b/include/vase/search.h new file mode 100644 index 0000000..4db59b9 --- /dev/null +++ b/include/vase/search.h @@ -0,0 +1,19 @@ +#pragma once + +#include "pch.h" +#include "vase/shard.h" + +struct RegexGroup { + uint32_t start; + uint32_t end; + bool matched; +}; + +struct RegexMatch { + uint32_t start; + uint32_t end; + std::vector groups{}; +}; + +const std::vector regex_search(Shard *root, std::string_view pattern_str, uint32_t start_offset, uint32_t end_offset, uint32_t flags, bool global); +void print_regex(const std::vector &matches); diff --git a/include/vase/shard.h b/include/vase/shard.h index de0fa3f..655b2ca 100644 --- a/include/vase/shard.h +++ b/include/vase/shard.h @@ -70,4 +70,6 @@ Shard *merge(Shard *a, Shard *b); Shard *merge_leaves(Shard *a, Shard *b); Shard *append_leaf(Shard *root, Shard *leaf); +uint32_t offset_of(Shard *s, uint32_t line_number, uint32_t col); + void print_shard(const Shard *shard, int depth = 0); diff --git a/include/vase/vase.h b/include/vase/vase.h index aae65b8..647e421 100644 --- a/include/vase/vase.h +++ b/include/vase/vase.h @@ -2,8 +2,10 @@ #include "buffer/append.h" #include "buffer/original.h" -#include "line_iter.h" +#include "iterators/chunk.h" +#include "iterators/line.h" #include "pch.h" +#include "search.h" #include "shard.h" struct Vase { @@ -16,95 +18,18 @@ struct Vase { Shard *root; - Vase(char *data, uint32_t length) : original(data, length), append() { - root = new Petal(length, original.newlines.size(), &original, 0); - } + Vase(char *data, uint32_t length); - ~Vase() { - Shard::release(root); - } + ~Vase(); - uint32_t length() { - return root->length; - } + uint32_t length(); - std::string to_string() { - std::string out; - flatten(root, out); - return out; - } + std::string to_string(); + static void flatten(Shard *s, std::string &out); - void type(uint32_t offset, char key) { - uint32_t pos = append.key(key); - Shard *inserted = new Petal(1, key == '\n', &append, pos); - auto [left, right] = split_shard(root, offset); - Shard *left2 = append_leaf(left, inserted); - Shard *new_root = concat_shard(left2, right); - Shard::release(left); - Shard::release(right); - Shard::release(left2); - Shard::release(inserted); - Shard::release(root); - root = new_root; - } + void type(uint32_t offset, char key); + void insert(uint32_t offset, const char *data, uint32_t len); + void erase(uint32_t cursor, int64_t amount); - void insert(uint32_t offset, const char *data, uint32_t len) { - uint32_t lines = 0; - uint32_t pos = append.append(data, len, &lines); - 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); - Shard::release(left); - Shard::release(right); - Shard::release(left2); - Shard::release(inserted); - Shard::release(root); - root = new_root; - } - - void erase(uint32_t cursor, int64_t amount) { - if (amount == 0) - return; - uint32_t start; - uint32_t count; - if (amount < 0) { - count = std::min(-amount, cursor); - start = cursor - count; - } else { - start = cursor; - count = amount; - } - auto [a, b] = split_shard(root, start); - auto [d, c] = split_shard(b, count); - Shard *new_root = concat_shard(a, c); - Shard::release(a); - Shard::release(b); - Shard::release(c); - Shard::release(d); - Shard::release(root); - root = new_root; - } - - void flatten(Shard *s, std::string &out) { - if (s->kind == Shard::ShardKind::Petal) { - auto *p = static_cast(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 = static_cast(s); - flatten(b->left, out); - flatten(b->right, out); - } - } - - uint32_t offset_of(uint32_t line_number, uint32_t col); + void regex_search_replace(std::string_view pattern, uint32_t start_offset, uint32_t end_offset, std::string_view replace, std::string_view options); }; diff --git a/src/main.cc b/src/main.cc index ba37426..afcf676 100644 --- a/src/main.cc +++ b/src/main.cc @@ -5,18 +5,15 @@ int main() { const char *text_o = - "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr\n" - "st\n" - "uv\n" - "wx\n" - "yzabcdefghijklmnopqrstuvwxyz\n"; + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; uint32_t len = strlen(text_o); char *text = strdup(text_o); Vase vase = Vase(text, len); - vase.insert(14, "gr\ntt", 5); + vase.insert(1, "bcgr\ntt", 5); + vase.insert(2, "cgr\ntt", 5); vase.insert(58, "gr\ntt", 5); vase.insert(100, "gr\ntt", 5); vase.insert(20, "gr\ntt", 5); @@ -26,17 +23,33 @@ int main() { vase.type(7, '3'); vase.type(8, '4'); - vase.erase(vase.offset_of(7, 3), -3); + vase.erase(offset_of(vase.root, 7, 3), -3); - print_shard(vase.root); + std::vector matches = regex_search(vase.root, "a(.)c", 0, vase.length(), PCRE2_MULTILINE, false); + print_regex(matches); + std::cout << "\n\n"; - std::cout << (int)vase.offset_of(6, 0) << "\n\n"; + std::cout << "\n\n" + << vase.to_string(); - LineIterator it(vase.root, 3); + // supports gisxUn + vase.regex_search_replace("a(.)c", 0, vase.length(), "|$1|", "g"); + + /*print_shard(vase.root); + + LineIterator it(vase.root, 0); std::string line; while (it.next(line)) std::cout << line << "\n"; + std::cout << "\n\n"; + + ChunkIterator it2(vase.root); + const char *data; + uint32_t length; + while (it2.next(&data, &length)) + std::cout << std::string(data, length) << "\n|\n";*/ + std::cout << "\n\n" << vase.to_string(); diff --git a/src/vase/buffer/original.cc b/src/vase/buffer/original.cc index 7beb704..cf02834 100644 --- a/src/vase/buffer/original.cc +++ b/src/vase/buffer/original.cc @@ -1,5 +1,21 @@ #include "vase/buffer/original.h" +OriginalBuffer::OriginalBuffer(char *buf, uint32_t length) : buf(buf), length(length) { + const char *p = buf; + const char *end = buf + length; + while (p < end) { + p = (const char *)memchr(p, '\n', end - p); + if (!p) + break; + newlines.push_back(uint32_t(p - buf)); + p++; + } +} + +OriginalBuffer::~OriginalBuffer() { + free(buf); +} + const char *OriginalBuffer::read(uint32_t pos, uint32_t *out_len) { if (pos >= length) return nullptr; diff --git a/src/vase/iterators/chunk.cc b/src/vase/iterators/chunk.cc new file mode 100644 index 0000000..fbd2bc5 --- /dev/null +++ b/src/vase/iterators/chunk.cc @@ -0,0 +1,75 @@ +#include "vase/iterators/chunk.h" + +ChunkIterator::ChunkIterator(Shard *r, uint32_t offset) : root(r) { + if (!root) + return; + Shard::retain(root); + seek(offset); +} + +ChunkIterator::~ChunkIterator() { + Shard::release(root); +} + +void ChunkIterator::seek(uint32_t offset) { + stack.clear(); + petal = nullptr; + petal_offset = 0; + if (!root || offset >= root->length) + return; + + uint32_t target = offset; + Shard *curr = root; + + while (curr) { + if (curr->kind == Shard::ShardKind::Petal) { + petal = (Petal *)curr; + petal_offset = target; + return; + } else { + auto *b = (Branch *)curr; + uint32_t left_len = b->left->length; + if (target < left_len) { + stack.push_back(b->right); + curr = b->left; + } else { + target -= left_len; + curr = b->right; + } + } + } +} + +bool ChunkIterator::next(const char **data, uint32_t *out_len) { + while (true) { + if (petal && petal_offset < petal->length) { + uint32_t remaining = petal->length - petal_offset; + uint32_t read_pos = petal->pos + petal_offset; + uint32_t got = 0; + const char *chunk = petal->source->read(read_pos, &got); + if (got == 0) { + petal = nullptr; + continue; + } + uint32_t take = std::min(got, remaining); + *data = chunk; + *out_len = take; + petal_offset += take; + return true; + } + if (stack.empty()) + return false; + Shard *s = stack.back(); + stack.pop_back(); + if (!s) + continue; + if (s->kind == Shard::ShardKind::Petal) { + petal = (Petal *)s; + petal_offset = 0; + } else { + auto *b = (Branch *)s; + stack.push_back(b->right); + stack.push_back(b->left); + } + } +} diff --git a/src/vase/iterators/line.cc b/src/vase/iterators/line.cc new file mode 100644 index 0000000..6654513 --- /dev/null +++ b/src/vase/iterators/line.cc @@ -0,0 +1,33 @@ +#include "vase/iterators/line.h" + +LineIterator::LineIterator(Shard *r, uint32_t start_line) + : offset(offset_of(r, start_line, 0)), it(r, offset) {} + +LineIterator::~LineIterator() {}; + +bool LineIterator::next(std::string &line) { + line.clear(); + while (true) { + if (remaining == 0) { + uint32_t got; + if (!it.next(&cursor, &got)) { + eof = true; + break; + } + remaining = got; + } + const char *nl = (const char *)memchr(cursor, '\n', remaining); + if (nl) { + const char *end = nl; + size_t len = end - cursor; + line.append(cursor, len); + cursor = end + 1; + remaining -= (uint32_t)(len + 1); + return true; + } + line.append(cursor, remaining); + cursor += remaining; + remaining = 0; + } + return !line.empty(); +} diff --git a/src/vase/line_iter.cc b/src/vase/line_iter.cc deleted file mode 100644 index 3590f76..0000000 --- a/src/vase/line_iter.cc +++ /dev/null @@ -1,98 +0,0 @@ -#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 = (Branch *)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; - Shard *s = root; - while (s->kind == Shard::ShardKind::Branch) { - auto *b = (Branch *)s; - if (nth <= b->left->lines) { - stack.push_back(b); - s = b->left; - } else { - nth -= b->left->lines; - s = b->right; - } - } - petal = (Petal *)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; -} diff --git a/src/vase/search.cc b/src/vase/search.cc new file mode 100644 index 0000000..c586de9 --- /dev/null +++ b/src/vase/search.cc @@ -0,0 +1,245 @@ +#include "vase/search.h" +#include "vase/iterators/chunk.h" + +const std::vector regex_search(Shard *root, std::string_view pattern_str, uint32_t start_offset, uint32_t end_offset, uint32_t flags, bool global) { + std::vector results; + + int errornumber; + PCRE2_SIZE erroroffset; + + PCRE2_SPTR pattern = (PCRE2_SPTR)pattern_str.data(); + + pcre2_code *re = pcre2_compile( + pattern, + pattern_str.size(), + flags, + &errornumber, + &erroroffset, + NULL + ); + + if (re == NULL) { + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(errornumber, buffer, sizeof(buffer)); + printf("Compilation failed at offset %d: %s\n", (int)erroroffset, buffer); + return results; + } + + pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL); + + ChunkIterator it(root, start_offset); + + const char *data; + uint32_t length; + + char buf[2048]; + + uint32_t global_offset = start_offset; + int64_t offset = -1; + + auto record_match = [&](int rc, PCRE2_SIZE *ovector) { + if (global_offset + (uint32_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] + }; + match.groups.reserve(rc - 1); + for (int i = 1; i < rc; ++i) { + PCRE2_SIZE s = ovector[2 * i]; + PCRE2_SIZE e = ovector[2 * i + 1]; + if (s == PCRE2_UNSET) + match.groups.push_back({0, 0, false}); + else + match.groups.push_back({global_offset + (uint32_t)s, global_offset + (uint32_t)e, true}); + } + results.push_back(std::move(match)); + }; + + auto skip_to_next_line = [&](const char *&data, uint32_t &length, int64_t &offset, uint32_t &global_offset) { + while (true) { + const char *p = (const char *)memchr(data + offset, '\n', length - offset); + if (p) { + uint32_t nl = p - data; + offset = nl + 1; + return; + } + global_offset += length; + offset = -1; + if (!it.next(&data, &length)) + return; + offset = 0; + } + }; + + while (global_offset < end_offset) { + if (offset == -1) { + bool more = it.next(&data, &length); + if (!more) + break; + offset = 0; + } + + while (offset < length) { + if (offset == -1) + break; + + int rc = pcre2_match( + re, + (PCRE2_SPTR)data, + (size_t)length, + offset, + PCRE2_PARTIAL_HARD, + match_data, + NULL + ); + + if (rc == PCRE2_ERROR_PARTIAL) { + uint32_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; + + if (partial_length >= 2048) { + global_offset += offset; + offset = -1; + continue; + } + + global_offset += partial_start; + + memcpy(buf, data + partial_start, partial_length); + buflen += partial_length; + + offset = -1; + bool exhausted = false; + + while (true) { + if (buflen >= 2048) + break; + + bool more = it.next(&data, &length); + if (!more) { + exhausted = true; + break; + } + uint32_t l = std::min(length, 2048 - buflen); + memcpy(buf + buflen, data, l); + buflen += l; + + int rc = pcre2_match( + re, + (PCRE2_SPTR)buf, + (size_t)buflen, + 0, + PCRE2_PARTIAL_HARD, + match_data, + NULL + ); + + if (rc == PCRE2_ERROR_PARTIAL) { + continue; + } else if (rc > 0) { + PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); + record_match(rc, ovector); + global_offset += buflen - l; + offset = l - (buflen - ovector[1]); + if (!global) + skip_to_next_line(data, length, offset, global_offset); + break; + } else if (rc == PCRE2_ERROR_NOMATCH) { + global_offset += buflen - l; + offset = 0; + break; + } else { + printf("A matching error occurred: %d\n", rc); + break; + } + } + + if (exhausted) { + uint32_t search_from = 0; + while (search_from <= buflen) { + int rc = pcre2_match( + re, + (PCRE2_SPTR)buf, + (size_t)buflen, + search_from, + 0, + match_data, + NULL + ); + if (rc <= 0) + break; + 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]; + } else { + const void *p = memchr(buf + ov[1], '\n', buflen - ov[1]); + if (!p) + break; + search_from = (const char *)p - buf + 1; + } + } + offset = -1; + } + + continue; + } else if (rc > 0) { + PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); + record_match(rc, ovector); + if (global) { + if ((int64_t)ovector[1] == offset) + offset++; + else + offset = ovector[1]; + } else { + offset = ovector[1]; + skip_to_next_line(data, length, offset, global_offset); + } + } else if (rc == PCRE2_ERROR_NOMATCH) { + offset = length; + break; + } else { + offset = length; + printf("A matching error occurred: %d\n", rc); + break; + } + } + + global_offset += offset; + offset = -1; + } + + pcre2_match_data_free(match_data); + pcre2_code_free(re); + + return results; +} + +void print_regex(const std::vector &matches) { + std::cout << "RegexMatches (" << matches.size() << "):\n"; + + for (size_t i = 0; i < matches.size(); ++i) { + const auto &match = matches[i]; + + std::cout << " [" << i << "] {\n"; + std::cout << " start: " << match.start << '\n'; + std::cout << " end: " << match.end << '\n'; + std::cout << " groups (" << match.groups.size() << "):\n"; + + for (size_t j = 0; j < match.groups.size(); ++j) { + const auto &group = match.groups[j]; + + std::cout << " [" << j << "] { " + << "matched: " << (group.matched ? "true" : "false") + << ", start: " << group.start + << ", end: " << group.end + << " }\n"; + } + + std::cout << " }\n"; + } +} diff --git a/src/vase/shard.cc b/src/vase/shard.cc index 4b4ca69..2b826b2 100644 --- a/src/vase/shard.cc +++ b/src/vase/shard.cc @@ -170,6 +170,30 @@ Shard *concat_shard(Shard *left, Shard *right) { return merge(left, right); } +uint32_t offset_of(Shard *s, uint32_t line_number, uint32_t col) { + if (line_number == 0) + return col; + + uint32_t nth = line_number; + uint32_t base_offset = 0; + + while (s->kind == Shard::ShardKind::Branch) { + auto *b = (Branch *)s; + if (nth <= b->left->lines) { + s = b->left; + } else { + nth -= b->left->lines; + base_offset += b->left->length; + s = b->right; + } + } + + auto *petal = (Petal *)s; + uint32_t nl_pos = petal->source->nth_newline(petal->pos, nth); + uint32_t offset_in_petal = (nl_pos - petal->pos) + 1; + return base_offset + offset_in_petal + col; +} + void print_shard(const Shard *shard, int depth) { if (!shard) { std::cout << std::string(depth * 2, ' ') << "\n"; @@ -180,7 +204,7 @@ void print_shard(const Shard *shard, int depth) { std::cout << indent; if (shard->kind == Shard::ShardKind::Branch) { - auto *branch = static_cast(shard); + auto *branch = (const Branch *)shard; std::cout << "Branch" @@ -197,7 +221,7 @@ void print_shard(const Shard *shard, int depth) { std::cout << indent << "└─ right:\n"; print_shard(branch->right, depth + 2); } else { - auto *petal = static_cast(shard); + auto *petal = (const Petal *)(shard); std::cout << "Petal" diff --git a/src/vase/vase.cc b/src/vase/vase.cc index 97c744a..2a22b7d 100644 --- a/src/vase/vase.cc +++ b/src/vase/vase.cc @@ -1,26 +1,276 @@ #include "vase/vase.h" +#include "vase/search.h" -uint32_t Vase::offset_of(uint32_t line_number, uint32_t col) { - if (line_number == 0) - return col; +Vase::Vase(char *data, uint32_t length) : original(data, length), append() { + root = new Petal(length, original.newlines.size(), &original, 0); +} - Shard *s = root; - uint32_t nth = line_number; - uint32_t base_offset = 0; +Vase::~Vase() { + Shard::release(root); +} - while (s->kind == Shard::ShardKind::Branch) { - auto *b = static_cast(s); - if (nth <= b->left->lines) { - s = b->left; - } else { - nth -= b->left->lines; - base_offset += b->left->length; - s = b->right; +uint32_t Vase::length() { + return root->length; +} + +std::string Vase::to_string() { + std::string out; + flatten(root, out); + return out; +} + +void Vase::type(uint32_t offset, char key) { + uint32_t pos = append.key(key); + Shard *inserted = new Petal(1, key == '\n', &append, pos); + auto [left, right] = split_shard(root, offset); + Shard *left2 = append_leaf(left, inserted); + Shard *new_root = concat_shard(left2, right); + Shard::release(left); + Shard::release(right); + Shard::release(left2); + Shard::release(inserted); + Shard::release(root); + root = new_root; +} + +void Vase::insert(uint32_t offset, const char *data, uint32_t len) { + uint32_t lines = 0; + uint32_t pos = append.append(data, len, &lines); + 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); + Shard::release(left); + Shard::release(right); + Shard::release(left2); + Shard::release(inserted); + Shard::release(root); + root = new_root; +} + +void Vase::erase(uint32_t cursor, int64_t amount) { + if (amount == 0) + return; + uint32_t start; + uint32_t count; + if (amount < 0) { + count = std::min(-amount, cursor); + start = cursor - count; + } else { + start = cursor; + count = amount; + } + auto [a, b] = split_shard(root, start); + auto [d, c] = split_shard(b, count); + Shard *new_root = concat_shard(a, c); + Shard::release(a); + Shard::release(b); + Shard::release(c); + Shard::release(d); + Shard::release(root); + root = new_root; +} + +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); + } +} + +struct ReplacePart { + enum struct PartType { + FullMatch, + CaptureGroup, + Constant + } type; + + std::variant value; +}; + +std::vector parse_replace(std::string_view s) { + std::vector parts; + std::string constant; + auto flush_constant = [&]() { + if (!constant.empty()) { + parts.push_back( + ReplacePart{ + .type = ReplacePart::PartType::Constant, + .value = std::move(constant) + } + ); + 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; +} + +static Shard *extract_range(Shard *tree, uint32_t start, uint32_t end) { + if (end <= start) + return nullptr; + auto [left, rest] = split_shard(tree, start); + auto [mid, right] = split_shard(rest, end - start); + Shard::release(left); + Shard::release(rest); + Shard::release(right); + return mid; +} + +static void append_piece(Shard *&accum, Shard *piece) { + if (!piece) + return; + if (!accum) { + accum = piece; + return; + } + Shard *combined = concat_shard(accum, piece); + Shard::release(accum); + Shard::release(piece); + accum = combined; +} + +void Vase::regex_search_replace(std::string_view pattern, uint32_t start_offset, uint32_t end_offset, std::string_view replace, std::string_view options) { + bool global = false; + uint32_t flags = 0; + + for (char c : options) { + switch (c) { + case 'g': + global = true; + break; + case 'i': + flags |= PCRE2_CASELESS; + break; + case 's': + flags |= PCRE2_DOTALL; + break; + case 'x': + flags |= PCRE2_EXTENDED; + break; + case 'U': + flags |= PCRE2_UNGREEDY; + break; + case 'n': + flags |= PCRE2_NO_AUTO_CAPTURE; + break; } } - auto *petal = static_cast(s); - uint32_t nl_pos = petal->source->nth_newline(petal->pos, nth); - uint32_t offset_in_petal = (nl_pos - petal->pos) + 1; - return base_offset + offset_in_petal + col; + const std::vector matches = regex_search(root, pattern, start_offset, end_offset, flags | PCRE2_MULTILINE, global); + if (matches.empty()) + return; + + std::vector replace_parts = parse_replace(replace); + + struct ConstantRef { + uint32_t pos = 0; + uint32_t lines = 0; + }; + std::vector constants(replace_parts.size()); + for (size_t i = 0; i < replace_parts.size(); ++i) { + if (replace_parts[i].type != ReplacePart::PartType::Constant) + continue; + const std::string &text = std::get(replace_parts[i].value); + uint32_t lines = 0; + uint32_t pos = append.append(text.data(), (uint32_t)text.size(), &lines); + constants[i] = {pos, lines}; + } + + for (auto it = matches.rbegin(); it != matches.rend(); ++it) { + const RegexMatch &match = *it; + + Shard *replacement = nullptr; + for (size_t i = 0; i < replace_parts.size(); ++i) { + const ReplacePart &part = replace_parts[i]; + switch (part.type) { + case ReplacePart::PartType::Constant: { + const std::string &text = std::get(part.value); + const ConstantRef &ref = constants[i]; + Shard *piece = new Petal((uint32_t)text.size(), ref.lines, &append, ref.pos); + append_piece(replacement, piece); + break; + } + case ReplacePart::PartType::FullMatch: + append_piece(replacement, extract_range(root, match.start, match.end)); + break; + case ReplacePart::PartType::CaptureGroup: { + uint8_t idx = std::get(part.value); + if (idx <= match.groups.size()) { + const RegexGroup &group = match.groups[idx - 1]; + if (group.matched) + append_piece(replacement, extract_range(root, group.start, group.end)); + } + break; + } + } + } + + auto [left, rest] = split_shard(root, match.start); + auto [dropped, right] = split_shard(rest, match.end - match.start); + Shard::release(dropped); + Shard::release(rest); + + Shard *new_root; + if (replacement) { + Shard *left2 = concat_shard(left, replacement); + Shard::release(left); + Shard::release(replacement); + new_root = concat_shard(left2, right); + Shard::release(left2); + } else { + new_root = concat_shard(left, right); + Shard::release(left); + } + Shard::release(right); + Shard::release(root); + root = new_root; + } }