#include "internal/vase/vase.h" namespace bed::internal::vase { std::vector parse_replace(AppendStorage *ap, std::string_view s) { std::vector parts; std::string constant; auto flush_constant = [&]() { if (!constant.empty()) { uint64_t lines = 0; const char *p = constant.data(); const char *end = p + constant.size(); while ((p = (const char *)memchr(p, '\n', end - p))) { ++lines; ++p; } uint64_t pos = ap->append(constant.data(), (uint64_t)constant.size()); parts.push_back( ReplacePart{ .type = ReplacePart::PartType::Constant, .value = new Petal((uint64_t)constant.size(), lines, ap, pos) } ); constant.clear(); } }; for (size_t i = 0; i < s.size(); ++i) { char c = s[i]; 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 } ); } else if (next >= '1' && next <= '9') { flush_constant(); parts.push_back( ReplacePart{ .type = ReplacePart::PartType::CaptureGroup, .value = (uint8_t)(next - '0') } ); } else if (next == 'n') { constant.push_back('\n'); } else { constant.push_back(next); } ++i; continue; } if (c == '&') { flush_constant(); parts.push_back( ReplacePart{ .type = ReplacePart::PartType::FullMatch, .value = (uint8_t)0 } ); } constant.push_back(c); } flush_constant(); return parts; } Shard *substitute( AppendStorage *ap, Shard *root, std::string_view pattern, uint64_t start, uint64_t end, std::string_view replace, std::string_view options, const std::function &on_edit ) { if (!start || !end) throw ed_error("Invalid range."); start--; end--; if (!root) throw ed_error("line range out of bounds"); uint64_t line_count = root->lines + 1; if (start > end || end >= line_count) throw ed_error("line range out of bounds"); uint64_t start_offset = offset_of(root, start); uint64_t end_offset = (end + 1 == line_count) ? root->length : offset_of(root, end + 1) - 1; std::vector matches = _regex_search(root, pattern, start_offset, end_offset, options); if (matches.empty()) return root; std::vector replace_parts = parse_replace(ap, replace); struct Edit { uint64_t line; uint64_t old_lines; uint64_t new_lines; }; uint64_t orig_line = start; int64_t line_delta = 0; 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] = Shard::split(remaining, gap); Shard::release(remaining); pieces.push_back(keep); remaining = rest; orig_line += keep ? keep->lines : 0; } auto [dropped, rest2] = Shard::split(remaining, match.end - match.start); Shard::release(remaining); remaining = rest2; uint64_t old_lines = dropped ? dropped->lines : 0; uint64_t new_lines = 0; for (size_t i = 0; i < replace_parts.size(); ++i) { const ReplacePart &part = replace_parts[i]; switch (part.type) { case ReplacePart::PartType::Constant: { Shard *c = std::get(part.value); Shard::retain(c); pieces.push_back(c); new_lines += c ? c->lines : 0; break; } case ReplacePart::PartType::FullMatch: Shard::retain(dropped); pieces.push_back(dropped); new_lines += dropped ? dropped->lines : 0; 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 != UINT64_MAX) { uint64_t ls = group.start - match.start; uint64_t le = group.end - match.start; auto [a, b] = Shard::split(dropped, ls); auto [g, c] = Shard::split(b, le - ls); Shard::release(a); Shard::release(b); Shard::release(c); pieces.push_back(g); new_lines += g ? g->lines : 0; } } break; } } } Shard::release(dropped); if (old_lines || new_lines) { uint64_t report_line = (uint64_t)((int64_t)orig_line + line_delta); if (on_edit) on_edit(report_line, old_lines, new_lines); line_delta += (int64_t)new_lines - (int64_t)old_lines; } orig_line += old_lines; 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 : Shard::build(compact.data(), 0, compact.size()); Shard::release(root); return new_root; } uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) { if (start == 0 || start > root->lines + 1) throw ed_error("Invalid line number."); start--; std::vector results; int errornumber; PCRE2_SIZE erroroffset; pcre2_code *re = pcre2_compile( (PCRE2_SPTR)pattern.data(), pattern.size(), PCRE2_UTF | PCRE2_EXTENDED, &errornumber, &erroroffset, NULL ); if (re == NULL) throw ed_error("Can't compile regex."); pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr); if (!match_data) { pcre2_code_free(re); throw ed_error("Can't create regex match data."); } uint64_t at = (start + 1) % (root->lines + 1); LineIterator it(root, at, Direction::Forward); std::string line; while (it.next(&line)) { int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); if (rc >= 0) { pcre2_match_data_free(match_data); pcre2_code_free(re); return at + 1; } if (rc != PCRE2_ERROR_NOMATCH) { pcre2_match_data_free(match_data); pcre2_code_free(re); throw ed_error("Regex matching failed."); } at++; } at = 0; LineIterator it2(root, at, Direction::Forward); while (it2.next(&line)) { if (at > start) break; int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); if (rc >= 0) { pcre2_match_data_free(match_data); pcre2_code_free(re); return at + 1; } if (rc != PCRE2_ERROR_NOMATCH) { pcre2_match_data_free(match_data); pcre2_code_free(re); throw ed_error("Regex matching failed."); } at++; } pcre2_match_data_free(match_data); pcre2_code_free(re); throw ed_error("No line matched."); } uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) { if (start == 0 || start > root->lines + 1) throw ed_error("Invalid line number."); start--; std::vector results; int errornumber; PCRE2_SIZE erroroffset; pcre2_code *re = pcre2_compile( (PCRE2_SPTR)pattern.data(), pattern.size(), PCRE2_UTF | PCRE2_EXTENDED, &errornumber, &erroroffset, NULL ); if (re == NULL) throw ed_error("Can't compile regex."); pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr); if (!match_data) { pcre2_code_free(re); throw ed_error("Can't create regex match data."); } uint64_t at = (start == 0 ? root->lines : start - 1); LineIterator it(root, at, Direction::Backward); std::string line; while (it.next(&line)) { int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); if (rc >= 0) { pcre2_match_data_free(match_data); pcre2_code_free(re); return at + 1; } if (rc != PCRE2_ERROR_NOMATCH) { pcre2_match_data_free(match_data); pcre2_code_free(re); throw ed_error("Regex matching failed."); } at--; } at = root->lines; LineIterator it2(root, at, Direction::Backward); while (it2.next(&line)) { if (at < start) break; int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); if (rc >= 0) { pcre2_match_data_free(match_data); pcre2_code_free(re); return at + 1; } if (rc != PCRE2_ERROR_NOMATCH) { pcre2_match_data_free(match_data); pcre2_code_free(re); throw ed_error("Regex matching failed."); } at--; } pcre2_match_data_free(match_data); pcre2_code_free(re); throw ed_error("No line matched."); } } // namespace bed::internal::vase