Rearrange project files.

This commit is contained in:
2026-08-06 20:16:40 +01:00
parent 9841c2cf15
commit 0720a254a3
22 changed files with 16 additions and 70 deletions
+70
View File
@@ -0,0 +1,70 @@
#include "internal/vase/buffer/append.h"
AppendBuffer::AppendBuffer(std::filesystem::path base_dir) {
base_dir /= "tapp.XXXXXX";
char *s = strdup(base_dir.c_str());
fd = mkstemp(s);
if (fd == -1)
throw std::runtime_error("mkstemp failed");
unlink(s);
free(s);
allocated_capacity = 1ull << 30;
if (ftruncate(fd, allocated_capacity) == -1)
throw std::runtime_error("ftruncate failed");
buf = (char *)mmap(nullptr, allocated_capacity, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED)
throw std::runtime_error("mmap failed");
}
AppendBuffer::~AppendBuffer() {
if (buf && buf != MAP_FAILED)
munmap(buf, allocated_capacity);
if (fd != -1)
close(fd);
}
void AppendBuffer::grow(uint64_t len) {
if (current_size + len > allocated_capacity) {
uint64_t new_capacity = allocated_capacity * 2;
if (new_capacity < current_size + len)
new_capacity = current_size + len + (1ull << 30);
if (ftruncate(fd, new_capacity) == -1)
throw std::runtime_error("ftruncate failed");
#if defined(__linux__)
void *new_buf = mremap(buf, allocated_capacity, new_capacity, MREMAP_MAYMOVE);
if (new_buf == MAP_FAILED)
throw std::runtime_error("mremap failed");
#else
munmap(buf, allocated_capacity);
void *new_buf = mmap(nullptr, new_capacity, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (new_buf == MAP_FAILED)
throw std::runtime_error("mmap failed");
#endif
allocated_capacity = new_capacity;
buf = (char *)new_buf;
}
}
uint64_t AppendBuffer::append(const char c) {
grow(1);
buf[current_size++] = c;
return current_size - 1;
}
uint64_t AppendBuffer::append(const char *text, uint64_t len) {
grow(len);
memcpy(buf + current_size, text, len);
uint64_t old_pos = current_size;
current_size += len;
return old_pos;
}
const char *AppendBuffer::read(uint64_t pos) {
if (pos >= current_size)
return nullptr;
return buf + pos;
}
uint64_t AppendBuffer::length() {
return current_size;
}
+45
View File
@@ -0,0 +1,45 @@
#include "internal/vase/buffer/original.h"
OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) {
if (!std::filesystem::exists(base_dir) || !std::filesystem::is_directory(base_dir))
throw std::runtime_error("Swap directory does not exist or is not a directory.");
base_dir /= "tbuf.XXXXXX";
char *s = strdup(base_dir.c_str());
fd = mkstemp(s);
if (fd == -1)
throw std::runtime_error("mkstemp failed");
unlink(s);
free(s);
}
OriginalBuffer::~OriginalBuffer() {
if (buf)
munmap((char *)buf, len);
if (fd != -1)
close(fd);
}
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");
}
madvise((void *)buf, len, MADV_RANDOM);
close(fd);
fd = -1;
}
const char *OriginalBuffer::read(uint64_t pos) {
if (pos >= len)
return nullptr;
return buf + pos;
}
uint64_t OriginalBuffer::length() {
return len;
}
+73
View File
@@ -0,0 +1,73 @@
#include "internal/vase/iterators/line.h"
LineIterator::LineIterator(Shard *root, uint64_t line_num, Direction dir)
: it(root, dir), dir(dir) {
it.seek_line(line_num);
it.next(&chunk, &len);
current_offset = it.byte_offset();
last_line_offset = current_offset;
}
bool LineIterator::next(std::string *line) {
if (!line || !chunk)
return false;
last_line_offset = current_offset;
line->clear();
if (dir == Direction::Forward) {
retry_forward:
const char *nl = (const char *)memchr(chunk, '\n', len);
if (!nl) {
if (len && chunk[len - 1] == '\r')
len--;
line->append(chunk, len);
current_offset += len;
if (!it.next(&chunk, &len))
return true;
goto retry_forward;
}
const char *end = nl;
if (end - chunk > 0 && *(end - 1) == '\r')
end--;
line->append(chunk, end);
size_t consumed = (nl - chunk) + 1;
chunk += consumed;
len -= consumed;
current_offset += consumed;
return true;
} else {
retry_backward:
#if defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__)
const char *nl = (const char *)memrchr(chunk, '\n', len);
#else
const char *nl = nullptr;
const char *p = chunk + len;
while (!nl && p != chunk)
if (*(--p) == '\n')
nl = p;
#endif
if (!nl) {
if (len && chunk[len - 1] == '\r')
len--;
line->insert(0, chunk, len);
current_offset -= len;
if (!it.next(&chunk, &len))
return true;
goto retry_backward;
}
const char *start = nl + 1;
const char *line_end = chunk + len;
const size_t consumed = line_end - nl;
const char *end = line_end;
if (end > start && *(end - 1) == '\r')
end--;
if (end > start)
line->insert(0, start, end - start);
len = nl - chunk;
current_offset -= consumed;
return true;
}
}
uint64_t LineIterator::byte_offset() {
return last_line_offset;
}
+158
View File
@@ -0,0 +1,158 @@
#include "internal/vase/iterators/petal.h"
PetalIterator::PetalIterator(Shard *r, Direction dir)
: dir(dir), root(r) {
if (!root)
return;
}
void PetalIterator::seek_offset(uint64_t offset) {
stack.clear();
petal = nullptr;
global_offset = 0;
last_offset = 0;
global_line = 0;
if (!root)
return;
if (offset >= root->length)
offset = root->length - 1;
uint64_t target = offset;
Shard *curr = root;
while (curr) {
if (curr->kind == Shard::Kind::Petal) {
petal = (Petal *)curr;
petal_offset = target;
global_offset += target;
last_offset = global_offset;
return;
} else {
auto *b = (Branch *)curr;
uint64_t left_len = b->left->length;
if (target < left_len) {
if (dir == Direction::Forward)
stack.push_back(b->right);
curr = b->left;
} else {
target -= left_len;
if (dir == Direction::Backward)
stack.push_back(b->left);
global_offset += left_len;
global_line += b->left->lines;
curr = b->right;
}
}
}
std::unreachable();
}
void PetalIterator::seek_line(uint64_t line) {
stack.clear();
petal = nullptr;
global_offset = 0;
last_offset = 0;
bool last_line = false;
if (!root)
return;
if (line > root->lines)
line = root->lines;
if (line == root->lines)
last_line = true;
if (!last_line && dir == Direction::Backward)
line++;
Shard *curr = root;
while (curr) {
if (curr->kind == Shard::Kind::Petal) {
petal = (Petal *)curr;
if (last_line && dir == Direction::Backward) {
petal_offset = petal->length;
global_offset += petal->length;
} else {
const char *text = petal->source->read(petal->pos);
uint64_t offset = 0;
while (line) {
const char *c = (const char *)memchr(text, '\n', petal->length - offset);
if (!c)
throw std::runtime_error("leaf line count is wrong.");
offset += (c - text) + 1;
text = c + 1;
line--;
}
if (dir == Direction::Backward)
--offset;
petal_offset = offset;
global_offset += offset;
}
last_offset = global_offset;
return;
} else {
auto *b = (Branch *)curr;
uint64_t left_lines = b->left->lines;
if (line <= left_lines) {
if (dir == Direction::Forward)
stack.push_back(b->right);
curr = b->left;
} else {
line -= left_lines;
if (dir == Direction::Backward)
stack.push_back(b->left);
global_offset += b->left->length;
curr = b->right;
}
}
}
std::unreachable();
}
Petal *PetalIterator::_next(uint64_t *offset) {
while (true) {
if (petal) {
auto ret = petal;
last_offset = global_offset;
if (dir == Direction::Forward)
global_offset += petal_offset;
else
global_offset -= petal_offset;
petal = nullptr;
*offset = petal_offset;
return ret;
}
if (stack.empty())
return nullptr;
auto s = stack.back();
stack.pop_back();
while (s->kind == Shard::Kind::Branch) {
Branch *b = (Branch *)s;
if (dir == Direction::Forward) {
stack.push_back(b->right);
s = b->left;
} else {
stack.push_back(b->left);
s = b->right;
}
}
petal = (Petal *)s;
petal_offset = dir == Direction::Forward ? 0 : petal->length;
}
}
bool PetalIterator::next(const char **data, uint64_t *out_len) {
uint64_t offset = 0;
Petal *p = _next(&offset);
if (!p) {
*data = nullptr;
*out_len = 0;
return false;
}
if (dir == Direction::Forward) {
*out_len = p->length - offset;
*data = p->source->read(p->pos + offset);
} else {
*out_len = offset;
*data = p->source->read(p->pos);
}
return true;
}
uint64_t PetalIterator::byte_offset() {
return last_offset;
}
+151
View File
@@ -0,0 +1,151 @@
#include "internal/vase/vase.h"
std::vector<ReplacePart> Vase::parse_replace(std::string_view s) {
std::vector<ReplacePart> 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<RegexMatch> matches = _regex_search(pattern, range, options);
if (matches.empty())
return;
std::vector<ReplacePart> replace_parts = parse_replace(replace);
std::vector<Shard *> 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;
}
auto [dropped, rest2] = Shard::split(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<Shard *>(part.value));
pieces.push_back(std::get<Shard *>(part.value));
break;
case ReplacePart::PartType::FullMatch:
Shard::retain(dropped);
pieces.push_back(dropped);
break;
case ReplacePart::PartType::CaptureGroup: {
uint8_t idx = std::get<uint8_t>(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] = 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);
}
}
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<Shard *>(part.value));
std::vector<Shard *> 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);
root = new_root;
}
std::vector<Range> Vase::regex_search(
std::string_view pattern, Range range, std::string_view options
) {
std::vector<RegexMatch> matches = _regex_search(pattern, range, options);
if (matches.empty())
return {};
std::vector<Range> result;
result.reserve(matches.size());
for (auto match : matches)
result.push_back({point_of(match.start), point_of(match.end)});
return result;
}
+296
View File
@@ -0,0 +1,296 @@
#include "internal/vase/iterators/petal.h"
#include "internal/vase/vase.h"
std::vector<RegexMatch> Vase::_regex_search(
std::string_view pattern, Range range, std::string_view options
) {
bool global = false;
uint64_t flags = PCRE2_MULTILINE | PCRE2_UTF;
const char *dot = "(?:(?!\\n)\\X)";
for (char c : options) {
switch (c) {
case 'g':
global = true;
break;
case 'i':
flags |= PCRE2_CASELESS;
break;
case 's':
dot = "(?:\\X)";
break;
case 'x':
flags |= PCRE2_EXTENDED;
break;
case 'U':
flags |= PCRE2_UNGREEDY;
break;
case 'n':
flags |= PCRE2_NO_AUTO_CAPTURE;
break;
case 'm':
flags &= ~PCRE2_MULTILINE;
break;
}
}
std::vector<RegexMatch> results;
int errornumber;
PCRE2_SIZE erroroffset;
std::string out;
out.reserve(pattern.size() * 2);
bool escaped = false;
bool in_class = false;
bool in_quote = false;
for (size_t i = 0; i < pattern.size(); i++) {
char c = pattern[i];
if (escaped) {
out += c;
escaped = false;
continue;
}
if (c == '\\') {
out += c;
if (i + 1 < pattern.size()) {
char next = pattern[i + 1];
if (next == 'Q')
in_quote = true;
else if (next == 'E')
in_quote = false;
out += next;
i++;
continue;
}
escaped = true;
continue;
}
if (in_quote) {
out += c;
continue;
}
if (c == '[') {
in_class = true;
out += c;
continue;
}
if (c == ']' && in_class) {
in_class = false;
out += c;
continue;
}
if (c == '.' && !in_class) {
out += dot;
continue;
}
out += c;
}
pcre2_code *re = pcre2_compile(
(PCRE2_SPTR)out.data(),
out.size(),
flags,
&errornumber,
&erroroffset,
NULL
);
if (re == NULL)
return results;
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);
PetalIterator it(root, Direction::Forward);
it.seek_offset(start_offset);
const char *data;
uint64_t length;
char buf[2048];
uint64_t global_offset = start_offset;
uint64_t offset = UINT64_MAX;
auto record_match = [&](int rc, PCRE2_SIZE *ovector) {
if (global_offset + (uint64_t)ovector[1] > end_offset || ovector[0] == ovector[1])
return;
RegexMatch match{
.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 + (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, 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) {
uint64_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 == UINT64_MAX) {
bool more = it.next(&data, &length);
if (!more)
break;
offset = 0;
}
while (offset < length) {
if (offset == UINT64_MAX)
break;
int rc = pcre2_match(
re,
(PCRE2_SPTR)data,
(size_t)length,
offset,
PCRE2_PARTIAL_HARD,
match_data,
NULL
);
if (rc == PCRE2_ERROR_PARTIAL) {
uint64_t buflen = 0;
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
uint64_t partial_start = ovector[0];
uint64_t partial_length = length - partial_start;
if (partial_length >= 2048) {
global_offset += offset;
offset = UINT64_MAX;
continue;
}
global_offset += partial_start;
memcpy(buf, data + partial_start, partial_length);
buflen += partial_length;
offset = UINT64_MAX;
bool exhausted = false;
while (true) {
if (buflen >= 2048)
break;
bool more = it.next(&data, &length);
if (!more) {
exhausted = true;
break;
}
uint64_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 {
break;
}
}
if (exhausted) {
uint64_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]) ? (uint64_t)ov[1] + 1 : (uint64_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 = UINT64_MAX;
}
continue;
} else if (rc > 0) {
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
record_match(rc, ovector);
if (global) {
if (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;
break;
}
}
global_offset += offset;
offset = UINT64_MAX;
}
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return results;
}
+258
View File
@@ -0,0 +1,258 @@
#include "internal/vase/shard.h"
void Shard::retain(Shard *n) {
if (n)
n->refs++;
};
void Shard::release(Shard *n) {
if (!n || --n->refs > 0)
return;
if (n->kind == Shard::Kind::Branch) {
release(((Branch *)n)->left);
release(((Branch *)n)->right);
delete (Branch *)n;
} else {
delete (Petal *)n;
}
}
int height(Shard *n) {
return n ? n->height : 0;
}
int balance_factor(Shard *n) {
Branch *b = (Branch *)n;
return height(b->left) - height(b->right);
}
Shard *rotate_right(Branch *z) {
Branch *y = (Branch *)z->left;
Shard *middle = new Branch(y->right, z->right);
Shard *out = new Branch(y->left, middle);
Shard::release(middle);
Shard::release(z);
return out;
}
Shard *rotate_left(Branch *z) {
Branch *y = (Branch *)z->right;
Shard *middle = new Branch(z->left, y->left);
Shard *out = new Branch(middle, y->right);
Shard::release(middle);
Shard::release(z);
return out;
}
Shard *balance(Shard *node) {
if (!node || node->kind == Shard::Kind::Petal)
return node;
Branch *b = (Branch *)node;
int bf = balance_factor(node);
if (bf > 1) {
Branch *left = (Branch *)b->left;
if (balance_factor(left) < 0) {
Shard::retain(left);
auto new_left = rotate_left(left);
auto rebuilt = new Branch(new_left, b->right);
auto result = rotate_right((Branch *)rebuilt);
Shard::release(new_left);
Shard::release(b);
return result;
}
return rotate_right(b);
}
if (bf < -1) {
Branch *right = (Branch *)b->right;
if (balance_factor(right) > 0) {
Shard::retain(right);
auto new_right = rotate_right(right);
auto rebuilt = new Branch(b->left, new_right);
auto result = rotate_left((Branch *)rebuilt);
Shard::release(new_right);
Shard::release(b);
return result;
}
return rotate_left(b);
}
return node;
}
Shard *Shard::merge(Shard *a, Shard *b) {
if (!a)
return b ? (Shard::retain(b), b) : nullptr;
if (!b)
return (Shard::retain(a), a);
if (a->height > b->height + 1) {
Branch *ba = (Branch *)a;
Shard *r = merge(ba->right, b);
Shard *out = balance(new Branch(ba->left, r));
Shard::release(r);
return out;
}
if (b->height > a->height + 1) {
Branch *bb = (Branch *)b;
Shard *l = merge(a, bb->left);
Shard *out = balance(new Branch(l, bb->right));
Shard::release(l);
return out;
}
return balance(new Branch(a, b));
}
std::pair<Shard *, Shard *> Shard::split(Shard *n, uint64_t offset) {
if (!n)
return {nullptr, nullptr};
if (offset == 0) {
Shard::retain(n);
return {nullptr, n};
}
if (offset == n->length) {
Shard::retain(n);
return {n, nullptr};
}
if (n->kind == Shard::Kind::Branch) {
Branch *b = (Branch *)n;
if (offset < b->left->length) {
auto [a, b2] = split(b->left, offset);
Shard *right = merge(b2, b->right);
Shard::release(b2);
return {a, right};
} else {
auto [a, b2] = split(b->right, offset - b->left->length);
Shard *left = merge(b->left, a);
Shard::release(a);
return {left, b2};
}
} else {
Petal *p = (Petal *)n;
uint64_t count[2]{0};
const char *c = p->source->read(p->pos);
const char *start = c;
const char *end = c + p->length;
while (c < end) {
const char *nl = (const char *)memchr(c, '\n', end - c);
if (!nl)
break;
if ((uint64_t)(nl - start) < offset)
count[0]++;
else
count[1]++;
c = nl + 1;
}
auto left = new Petal(offset, count[0], p->source, p->pos);
auto right = new Petal(p->length - offset, count[1], p->source, p->pos + offset);
return {left, right};
}
}
Shard *Shard::merge_leaves(Shard *a, Shard *b) {
if (a->kind != Shard::Kind::Petal || b->kind != Shard::Kind::Petal)
return merge(a, b);
Petal *pa = (Petal *)a;
Petal *pb = (Petal *)b;
if (!(pa->source == pb->source && pa->pos + pa->length == pb->pos))
return merge(a, b);
return new Petal(
pa->length + pb->length,
pa->lines + pb->lines,
pa->source,
pa->pos
);
}
Shard *Shard::append(Shard *root, Shard *leaf) {
if (!root) {
Shard::retain(leaf);
return leaf;
}
if (root->kind == Shard::Kind::Petal && root->length < PETAL_SIZE_MAX)
return merge_leaves(root, leaf);
Branch *b = (Branch *)root;
auto new_right = append(b->right, leaf);
auto out = balance(new Branch(b->left, new_right));
Shard::release(new_right);
return out;
}
Shard *Shard::concat(Shard *left, Shard *right) {
return merge(left, right);
}
Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
if (hi - lo == 1)
return pieces[lo];
size_t mid = lo + (hi - lo) / 2;
Shard *left = build(pieces, lo, mid);
Shard *right = build(pieces, mid, hi);
Shard *node = new Branch(left, right);
Shard::release(left);
Shard::release(right);
return node;
}
Shard *Shard::from_file(std::filesystem::path 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;
uint64_t total = std::filesystem::file_size(path);
std::vector<Shard *> pieces;
uint64_t pos = 0;
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
char buf[PETAL_SIZE_MAX];
while (pos < total) {
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)
break;
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(pieces.data(), 0, pieces.size());
}
+399
View File
@@ -0,0 +1,399 @@
#include "internal/vase/vase.h"
#include "internal/vase/iterators/line.h"
#include "internal/vase/iterators/petal.h"
Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir)
: path(path), swapdir(swapdir) {
if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir))
throw std::runtime_error("Swap directory does not exist or is not a directory.");
append = new AppendBuffer(swapdir);
original = new OriginalBuffer(swapdir);
if (std::filesystem::is_regular_file(path))
root = Shard::from_file(path, original);
else
root = nullptr;
history.push_back(root);
Shard::retain(root);
history_top = 0;
}
Vase::~Vase() {
Shard::release(root);
for (auto s : history)
Shard::release(s);
delete original;
delete append;
}
uint64_t Vase::length() {
return root->length;
}
std::string Vase::to_string() {
std::string out;
PetalIterator 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;
}
std::string Vase::to_string(Range range) {
std::string out;
PetalIterator 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);
}
bool Vase::undo() {
if (history_top == 0)
return false;
Shard::release(root);
history_top--;
root = history[history_top];
Shard::retain(root);
return true;
}
bool Vase::redo() {
if (history_top + 1 >= history.size())
return false;
Shard::release(root);
history_top++;
root = history[history_top];
Shard::retain(root);
return true;
}
void Vase::snapshot() {
if (history[history_top] == root)
return;
while (history.size() > history_top + 1) {
Shard::release(history.back());
history.pop_back();
}
Shard::retain(root);
history.push_back(root);
history_top++;
}
void Vase::prune_history(uint64_t n) {
n = std::min(n, history_top);
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::ofstream file(path, std::ios::binary);
if (!file)
return false;
PetalIterator 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() {
return false;
}
void Vase::insert(Point *point, char key) {
uint64_t pos = append->append(key);
Shard *inserted = new Petal(1, key == '\n', append, pos);
auto [left, right] = Shard::split(root, offset_of(*point));
Shard *left2 = Shard::append(left, inserted);
Shard::release(left);
Shard::release(inserted);
Shard *new_root = Shard::concat(left2, right);
Shard::release(left2);
Shard::release(right);
Shard::release(root);
root = new_root;
if (key == '\n')
*point = {point->row + 1, 0};
else
point->col++;
}
void Vase::insert(Point *point, const char *data, uint64_t len) {
while (len) {
uint64_t chunk_size = std::min<uint64_t>(len, PETAL_SIZE_MAX);
_insert(point, data, chunk_size);
len -= chunk_size;
data += chunk_size;
}
}
void Vase::_insert(Point *point, const char *data, uint64_t len) {
if (len == 0)
return;
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;
while ((data = (const char *)memchr(data, '\n', end - data))) {
++lines;
last_line = ++data;
}
uint64_t col = 0;
uint64_t remaining = end - last_line;
while (remaining) {
uint64_t n = grapheme_next_character_break_utf8(last_line, remaining);
last_line += n;
remaining -= n;
++col;
}
if (lines) {
point->row += lines;
point->col = col;
} else {
point->col += col;
}
Shard *inserted = new Petal(len, lines, append, pos);
auto [left, right] = Shard::split(root, offset);
Shard *left2 = Shard::append(left, inserted);
Shard::release(left);
Shard::release(inserted);
Shard *new_root = Shard::concat(left2, right);
Shard::release(left2);
Shard::release(right);
Shard::release(root);
root = new_root;
}
void Vase::erase(Point *point, uint64_t amount, Direction dir) {
if (amount == 0)
return;
Point start = *point;
Point end = *point;
if (dir == Direction::Forward)
move_clusters(&end, amount, Direction::Forward);
else
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);
uint64_t count = end_offset - start_offset;
auto [a, b] = Shard::split(root, start_offset);
auto [d, c] = Shard::split(b, count);
Shard *new_root = Shard::concat(a, c);
Shard::release(a);
Shard::release(b);
Shard::release(c);
Shard::release(d);
Shard::release(root);
root = new_root;
if (dir == Direction::Backward)
*point = start;
}
void Vase::erase(Range range) {
Point start = range.start;
Point end = range.end;
uint64_t start_offset = offset_of(start);
uint64_t end_offset = offset_of(end);
uint64_t count = end_offset - start_offset;
auto [a, b] = Shard::split(root, start_offset);
auto [d, c] = Shard::split(b, count);
Shard *new_root = Shard::concat(a, c);
Shard::release(a);
Shard::release(b);
Shard::release(c);
Shard::release(d);
Shard::release(root);
root = new_root;
}
void Vase::replace(Range range, const char *data, uint64_t len) {
erase(range);
insert(&range.start, data, len);
}
uint64_t Vase::offset_of(Point point) {
LineIterator it(root, point.row, Direction::Forward);
std::string line;
uint64_t offset = 0;
if (it.next(&line)) {
const char *ptr = line.data();
uint64_t remaining = line.length();
while (point.col && remaining) {
uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
remaining -= next_len;
ptr += next_len;
offset += next_len;
point.col--;
}
}
return it.byte_offset() + offset;
}
Point Vase::point_of(uint64_t offset) {
PetalIterator it(root, Direction::Backward);
it.seek_offset(offset);
Point p;
p.row = it.global_line;
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;
const char *p = chunk + len;
while (!nl && p != chunk)
if (*(--p) == '\n')
nl = p;
#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 (dir == Direction::Backward) {
LineIterator it(root, point->row, Direction::Backward);
while (amount) {
std::string line;
if (!it.next(&line))
return;
std::vector<uint64_t> clusters;
const char *ptr = line.data();
uint64_t remaining = line.size();
uint64_t byte = 0;
while (remaining) {
clusters.push_back(byte);
uint64_t len =
grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
byte += len;
}
while (amount && point->col) {
point->col--;
amount--;
}
if (amount == 0)
return;
if (point->row == 0)
return;
point->row--;
point->col = clusters.size();
amount--;
}
} else {
LineIterator it(root, point->row, Direction::Forward);
while (amount) {
std::string line;
if (!it.next(&line))
return;
if (point->col || amount) {
const char *ptr = line.data();
uint64_t remaining = line.size();
uint64_t col = 0;
while (col < point->col && remaining) {
uint64_t len = grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
col++;
}
while (amount && remaining) {
uint64_t len = grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
point->col++;
amount--;
}
}
if (amount) {
point->row++;
point->col = 0;
amount--;
}
}
}
}
void Vase::clamp(Point *point) {
if (point->row > root->lines)
point->row = root->lines;
LineIterator it(root, point->row, Direction::Forward);
std::string line;
uint64_t clusters = 0;
if (it.next(&line)) {
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;
clusters++;
}
}
if (point->col > clusters)
point->col = clusters;
}
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);
}