Major changes to api and structure.

- Remove newline caching
- limit petal sizes
- make public facing api use points only
- fix iterators (chunk & line)
This commit is contained in:
2026-08-02 11:07:04 +01:00
parent 89b24a7488
commit 51e193d28b
16 changed files with 355 additions and 422 deletions
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include "pch.h"
struct Point {
uint32_t row;
uint32_t col;
};
+2 -18
View File
@@ -12,31 +12,15 @@ struct AppendBuffer : Buffer {
uint32_t current_offset{0}; uint32_t current_offset{0};
uint32_t t_offset{0}; uint32_t t_offset{0};
using LChunk = uint32_t[CHUNK_SIZE];
std::vector<LChunk *> newlines;
LChunk *l_current;
uint32_t l_offset{0};
AppendBuffer(); AppendBuffer();
~AppendBuffer(); ~AppendBuffer();
uint32_t key(char c); uint32_t key(char c);
uint32_t append(const char *text, uint32_t length, uint32_t *line_count); uint32_t append(const char *text, uint32_t length);
const char *read(uint32_t pos, uint32_t *out_len); const char *read(uint32_t pos, uint32_t *out_len);
uint32_t count_lines(uint32_t pos, uint32_t length); inline uint32_t length();
uint32_t next_newline(uint32_t pos);
uint32_t nth_newline(uint32_t pos, uint32_t n);
private:
inline uint32_t newline_value_at(uint32_t flat_index);
inline void new_text_chunk(); inline void new_text_chunk();
inline void new_line_chunk();
inline uint32_t find_newline(uint32_t target, uint32_t low, uint32_t high);
inline uint32_t _append(const char *text, uint32_t length);
}; };
+1 -3
View File
@@ -4,8 +4,6 @@
struct Buffer { struct Buffer {
virtual const char *read(uint32_t pos, uint32_t *out_len) = 0; virtual const char *read(uint32_t pos, uint32_t *out_len) = 0;
virtual uint32_t count_lines(uint32_t pos, uint32_t length) = 0; virtual inline uint32_t length() = 0;
virtual uint32_t next_newline(uint32_t pos) = 0;
virtual uint32_t nth_newline(uint32_t pos, uint32_t n) = 0;
virtual ~Buffer() = default; virtual ~Buffer() = default;
}; };
+3 -6
View File
@@ -5,16 +5,13 @@
struct OriginalBuffer : Buffer { struct OriginalBuffer : Buffer {
char *buf; char *buf;
uint32_t length; uint32_t len;
std::vector<uint32_t> newlines;
// TODO: replace with file io or even lazy loaded file io for large files/logs etc. later. // TODO: replace with file io or even lazy loaded file io for large files/logs etc. later.
OriginalBuffer(char *buf, uint32_t length); OriginalBuffer(char *buf, uint32_t len);
~OriginalBuffer(); ~OriginalBuffer();
const char *read(uint32_t pos, uint32_t *out_len); const char *read(uint32_t pos, uint32_t *out_len);
uint32_t count_lines(uint32_t pos, uint32_t length); uint32_t length();
uint32_t next_newline(uint32_t pos);
uint32_t nth_newline(uint32_t pos, uint32_t n);
}; };
+5 -2
View File
@@ -8,11 +8,14 @@ struct ChunkIterator {
std::vector<Shard *> stack; std::vector<Shard *> stack;
Petal *petal = nullptr; Petal *petal = nullptr;
uint32_t petal_offset = 0; uint32_t petal_offset = 0;
uint32_t global_offset;
ChunkIterator(Shard *r, uint32_t offset = 0); ChunkIterator(Shard *r);
~ChunkIterator(); ~ChunkIterator();
void seek(uint32_t offset); void seek_offset(uint32_t offset);
void seek_line(uint32_t offset);
bool next(const char **data, uint32_t *out_len); bool next(const char **data, uint32_t *out_len);
uint32_t byte_offset();
}; };
+4 -3
View File
@@ -5,16 +5,17 @@
#include "pch.h" #include "pch.h"
struct LineIterator { struct LineIterator {
uint32_t offset;
ChunkIterator it; ChunkIterator it;
const char *cursor = nullptr; const char *cursor = nullptr;
uint32_t remaining = 0;
bool eof = false; bool eof = false;
uint32_t remaining = 0;
uint32_t line_offset = 0;
LineIterator(Shard *r, uint32_t start_line); LineIterator(Shard *r, uint32_t start_line);
~LineIterator(); ~LineIterator() = default;
bool next(std::string &line); bool next(std::string &line);
uint32_t byte_offset();
}; };
+7 -20
View File
@@ -1,7 +1,9 @@
#pragma once #pragma once
#include "buffer/buffer.h" #include "buffer/buffer.h"
#include "buffer/original.h"
#include "pch.h" #include "pch.h"
#include "utils/utils.h"
struct Shard { struct Shard {
enum struct ShardKind : uint8_t { enum struct ShardKind : uint8_t {
@@ -19,17 +21,8 @@ struct Shard {
Shard(ShardKind kind, uint32_t length, uint32_t lines, uint8_t height) Shard(ShardKind kind, uint32_t length, uint32_t lines, uint8_t height)
: kind(kind), height(height), length(length), lines(lines), refs(1) {}; : kind(kind), height(height), length(length), lines(lines), refs(1) {};
virtual ~Shard() = default; static void retain(Shard *n);
static void release(Shard *n);
static void retain(Shard *n) {
n->refs++;
}
static void release(Shard *n) {
if (!n || --n->refs > 0)
return;
delete n;
}
}; };
struct Branch : Shard { struct Branch : Shard {
@@ -47,11 +40,6 @@ struct Branch : Shard {
retain(left); retain(left);
retain(right); retain(right);
}; };
~Branch() {
release(left);
release(right);
}
}; };
struct Petal : Shard { struct Petal : Shard {
@@ -64,12 +52,11 @@ struct Petal : Shard {
source(source), pos(pos) {}; source(source), pos(pos) {};
}; };
Shard *create_shards(Buffer *o);
std::pair<Shard *, Shard *> split_shard(Shard *n, uint32_t offset); std::pair<Shard *, Shard *> split_shard(Shard *n, uint32_t offset);
Shard *concat_shard(Shard *left, Shard *right); Shard *concat_shard(Shard *left, Shard *right);
Shard *merge(Shard *a, Shard *b); Shard *merge(Shard *a, Shard *b);
Shard *merge_leaves(Shard *a, Shard *b); Shard *merge_leaves(Shard *a, Shard *b);
Shard *append_leaf(Shard *root, Shard *leaf); Shard *append_leaf(Shard *root, Shard *leaf);
Shard *build_balanced(Shard **pieces, uint32_t lo, uint32_t hi);
uint32_t offset_of(Shard *s, uint32_t line_number, uint32_t col);
void print_shard(const Shard *shard, int depth = 0);
+21 -23
View File
@@ -2,46 +2,44 @@
#include "buffer/append.h" #include "buffer/append.h"
#include "buffer/original.h" #include "buffer/original.h"
#include "iterators/chunk.h"
#include "iterators/line.h" #include "iterators/line.h"
#include "pch.h" #include "pch.h"
#include "search.h" #include "search.h"
#include "shard.h" #include "shard.h"
#include "utils/utils.h"
struct Vase { struct Vase {
OriginalBuffer original;
AppendBuffer append;
/*std::vector<Shard *> undo; // TODO: later
uint8_t top; // of the undo stack.
uint8_t max; // for redo when no edits have been done after some undo.*/
Shard *root;
Vase(char *data, uint32_t length); Vase(char *data, uint32_t length);
~Vase(); ~Vase();
uint32_t length(); uint32_t length();
std::string to_string(); std::string to_string();
static void flatten(Shard *s, std::string &out);
void type(uint32_t offset, char key); Point resolve_column(uint32_t line, uint32_t column);
void insert(uint32_t offset, const char *data, uint32_t len);
void erase(uint32_t cursor, int64_t amount);
// Grapheme clusters or the specail cluster "\r\n' LineIterator iterate(uint32_t line);
void erase_clusters(uint32_t cursor, int64_t amount);
// moves in clusters from line,col and sets new position to line,col returns false if not enough space to jump.
bool jump_clusters(uint32_t *line, uint32_t *col, int64_t amount);
// need to also make helpers for line width.
void input(Point *point, char key);
void insert(Point *point, const char *data, uint32_t len);
void erase(Point *point, int64_t amount);
bool jump(Point *point, int64_t amount);
bool clamp(Point *point);
void regex_search_replace( void regex_search_replace(
std::string_view pattern, std::string_view pattern,
uint32_t start_offset, uint32_t end_offset, Point start, Point end,
std::string_view replace, std::string_view options std::string_view replace, std::string_view options
); );
private:
OriginalBuffer original;
AppendBuffer append;
Shard *root;
/*std::vector<Shard *> undo; // TODO: later
uint16_t top; // of the undo stack.
uint16_t max; // for redo when no edits have been done after some undo.*/
uint32_t offset_of(Point point);
static void flatten(Shard *s, std::string &out);
}; };
+15 -64
View File
@@ -1,17 +1,24 @@
#include "io/file.h" #include "io/file.h"
#include "pch.h" #include "pch.h"
#include "vase/iterators/chunk.h"
#include "vase/iterators/line.h"
#include "vase/shard.h" #include "vase/shard.h"
#include "vase/vase.h" #include "vase/vase.h"
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
/*const char *text_o =
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n" #ifdef DEBUG
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n"
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n" const char *text_o =
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz1\n"
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz2\n"
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz3\n"
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz4";
uint32_t len = strlen(text_o); uint32_t len = strlen(text_o);
char *text = strdup(text_o);*/ char *text = strdup(text_o);
#else
const char *path; const char *path;
@@ -24,65 +31,9 @@ int main(int argc, char *argv[]) {
char *text; char *text;
read_file(path, &text, &len); read_file(path, &text, &len);
auto start = std::chrono::steady_clock::now(); #endif
Vase vase = Vase(text, len); Vase vase = Vase(text, len);
auto end = std::chrono::steady_clock::now();
std::cout << "Time to load: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms\n";
std::cout << "Press Enter to continue...";
std::cin.get();
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);
vase.type(5, '1');
vase.type(6, '2');
vase.type(7, '3');
vase.type(8, '4');
vase.erase(offset_of(vase.root, 7, 3), -3);
// supports gisxUn and m as inverse (as multiline is default.)
// print_regex(matches); // debug
start = std::chrono::steady_clock::now();
std::vector<RegexMatch> matches = regex_search(vase.root, "here", 0, vase.length(), "");
end = std::chrono::steady_clock::now();
std::cout << "Time search: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms\n";
// print_shard(vase.root); // debug
/*LineIterator it(vase.root, 0);
std::string line;
while (it.next(line))
std::cout << line << "\n";*/
/*start = std::chrono::steady_clock::now();
vase.regex_search_replace("here", 0, vase.length(), "hello", "");
end = std::chrono::steady_clock::now();
std::cout << "Time replace: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms\n";*/
std::cout << "\n\n"
<< (int)vase.root->height << " append:" << (int)vase.append.current_offset;
std::cout << "Press Enter to continue...";
std::cin.get();
/*ChunkIterator it2(vase.root);
const char *data;
uint32_t length;
while (it2.next(&data, &length))
std::cout << std::string(data, length);*/
// std::cout << vase.to_string();
return 0; return 0;
} }
+10 -130
View File
@@ -2,140 +2,49 @@
AppendBuffer::AppendBuffer() { AppendBuffer::AppendBuffer() {
new_text_chunk(); new_text_chunk();
new_line_chunk();
} }
AppendBuffer::~AppendBuffer() { AppendBuffer::~AppendBuffer() {
for (auto p : buf) for (auto p : buf)
free(p); free(p);
for (auto p : newlines)
free(p);
} }
uint32_t AppendBuffer::key(char c) { uint32_t AppendBuffer::key(char c) {
if (t_offset == CHUNK_SIZE) if (t_offset == CHUNK_SIZE)
new_text_chunk(); new_text_chunk();
(*t_current)[t_offset++] = c; (*t_current)[t_offset++] = c;
return current_offset++;
if (c == '\n') {
if (l_offset == CHUNK_SIZE)
new_line_chunk();
(*l_current)[l_offset++] = current_offset;
}
++current_offset;
return current_offset - 1;
} }
uint32_t AppendBuffer::append(const char *text, uint32_t length, uint32_t *line_count) { uint32_t AppendBuffer::append(const char *text, uint32_t length) {
uint32_t start = current_offset; uint32_t start = current_offset;
while (length > 0) {
while (length) { if (t_offset == CHUNK_SIZE)
uint32_t space = CHUNK_SIZE - t_offset;
if (space == 0) {
new_text_chunk(); new_text_chunk();
space = CHUNK_SIZE; uint32_t copy = std::min(length, CHUNK_SIZE - t_offset);
} memcpy((*t_current) + t_offset, text, copy);
t_offset += copy;
uint32_t copy = length < space ? length : space; current_offset += copy;
*line_count += _append(text, copy);
text += copy; text += copy;
length -= copy; length -= copy;
} }
return start; return start;
} }
const char *AppendBuffer::read(uint32_t pos, uint32_t *out_len) { const char *AppendBuffer::read(uint32_t pos, uint32_t *out_len) {
if (pos >= current_offset) if (pos >= current_offset)
return nullptr; return nullptr;
uint32_t local_offset = pos % CHUNK_SIZE; uint32_t local_offset = pos % CHUNK_SIZE;
if (out_len) { if (out_len) {
uint32_t remaining = current_offset - pos; uint32_t remaining = current_offset - pos;
uint32_t until_chunk_end = CHUNK_SIZE - local_offset; uint32_t until_chunk_end = CHUNK_SIZE - local_offset;
*out_len = std::min(remaining, until_chunk_end); *out_len = std::min(remaining, until_chunk_end);
} }
return &(*buf[pos / CHUNK_SIZE])[local_offset]; return &(*buf[pos / CHUNK_SIZE])[local_offset];
} }
uint32_t AppendBuffer::find_newline(uint32_t target, uint32_t low, uint32_t high) { inline uint32_t AppendBuffer::length() {
if (low >= high) return current_offset;
return low;
uint32_t first_chunk = low / CHUNK_SIZE;
uint32_t last_chunk = (high - 1) / CHUNK_SIZE;
uint32_t left = first_chunk;
uint32_t right = last_chunk;
while (left < right) {
uint32_t mid = left + (right - left) / 2;
uint32_t first_value = (*newlines[mid])[0];
if (first_value < target)
left = mid + 1;
else
right = mid;
}
uint32_t chunk = left;
if (chunk > first_chunk) {
LChunk &prev = *newlines[chunk - 1];
uint32_t prev_size =
(chunk - 1 == newlines.size() - 1) ? l_offset : CHUNK_SIZE;
if (prev[prev_size - 1] >= target)
chunk--;
}
LChunk &c = *newlines[chunk];
uint32_t chunk_size =
(chunk == newlines.size() - 1) ? l_offset : CHUNK_SIZE;
uint32_t l = 0;
uint32_t r = chunk_size;
while (l < r) {
uint32_t m = l + (r - l) / 2;
if (c[m] < target)
l = m + 1;
else
r = m;
}
return chunk * CHUNK_SIZE + l;
}
inline uint32_t AppendBuffer::newline_value_at(uint32_t flat_index) {
uint32_t chunk = flat_index / CHUNK_SIZE;
uint32_t off = flat_index % CHUNK_SIZE;
return (*newlines[chunk])[off];
}
uint32_t AppendBuffer::next_newline(uint32_t pos) {
uint32_t total = CHUNK_SIZE * (uint32_t)(newlines.size() - 1) + l_offset;
uint32_t idx = find_newline(pos, 0, total);
return idx >= total ? UINT32_MAX : newline_value_at(idx);
}
uint32_t AppendBuffer::nth_newline(uint32_t pos, uint32_t n) {
uint32_t total = CHUNK_SIZE * (uint32_t)(newlines.size() - 1) + l_offset;
uint32_t idx0 = find_newline(pos, 0, total);
uint32_t idx = idx0 + (n - 1);
return idx >= total ? UINT32_MAX : newline_value_at(idx);
}
uint32_t AppendBuffer::count_lines(uint32_t pos, uint32_t length) {
if (newlines.empty())
return 0;
uint32_t total_newlines = CHUNK_SIZE * (newlines.size() - 1) + l_offset;
uint32_t pos_index = find_newline(pos, 0, total_newlines);
uint32_t end_index = find_newline(pos + length, pos_index, total_newlines);
return end_index - pos_index;
} }
inline void AppendBuffer::new_text_chunk() { inline void AppendBuffer::new_text_chunk() {
@@ -143,32 +52,3 @@ inline void AppendBuffer::new_text_chunk() {
buf.push_back(t_current); buf.push_back(t_current);
t_offset = 0; t_offset = 0;
} }
inline void AppendBuffer::new_line_chunk() {
l_current = (LChunk *)malloc(sizeof(LChunk));
newlines.push_back(l_current);
l_offset = 0;
}
inline uint32_t AppendBuffer::_append(const char *text, uint32_t length) {
memcpy((*t_current) + t_offset, text, length);
const char *p = text;
const char *end = text + length;
uint32_t start_index = l_offset;
while (p < end) {
p = (const char *)memchr(p, '\n', end - p);
if (!p)
break;
if (l_offset == CHUNK_SIZE)
new_line_chunk();
(*l_current)[l_offset++] = current_offset + uint32_t(p - text);
p++;
}
t_offset += length;
current_offset += length;
return l_offset - start_index;
}
+6 -39
View File
@@ -1,52 +1,19 @@
#include "vase/buffer/original.h" #include "vase/buffer/original.h"
OriginalBuffer::OriginalBuffer(char *buf, uint32_t length) : buf(buf), length(length) { OriginalBuffer::OriginalBuffer(char *buf, uint32_t len) : buf(buf), len(len) {}
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() { OriginalBuffer::~OriginalBuffer() {
free(buf); free(buf);
} }
const char *OriginalBuffer::read(uint32_t pos, uint32_t *out_len) { const char *OriginalBuffer::read(uint32_t pos, uint32_t *out_len) {
if (pos >= length) if (pos >= len)
return nullptr; return nullptr;
*out_len = length - pos; if (out_len)
*out_len = len - pos;
return buf + pos; return buf + pos;
} }
uint32_t OriginalBuffer::count_lines(uint32_t pos, uint32_t length) { uint32_t OriginalBuffer::length() {
auto start = std::lower_bound( return len;
newlines.begin(),
newlines.end(),
pos
);
auto end = std::lower_bound(
start,
newlines.end(),
pos + length
);
return (uint32_t)(end - start);
}
uint32_t OriginalBuffer::next_newline(uint32_t pos) {
auto it = std::lower_bound(newlines.begin(), newlines.end(), pos);
return it == newlines.end() ? UINT32_MAX : *it;
}
uint32_t OriginalBuffer::nth_newline(uint32_t pos, uint32_t n) {
auto it = std::lower_bound(newlines.begin(), newlines.end(), pos);
if (uint32_t(newlines.end() - it) < n)
return UINT32_MAX;
return *(it + (n - 1));
} }
+75 -18
View File
@@ -1,26 +1,24 @@
#include "vase/iterators/chunk.h" #include "vase/iterators/chunk.h"
ChunkIterator::ChunkIterator(Shard *r, uint32_t offset) : root(r) { ChunkIterator::ChunkIterator(Shard *r) : root(r) {
if (!root) if (!root)
return; return;
Shard::retain(root); Shard::retain(root);
seek(offset);
} }
ChunkIterator::~ChunkIterator() { ChunkIterator::~ChunkIterator() {
Shard::release(root); Shard::release(root);
} }
void ChunkIterator::seek(uint32_t offset) { void ChunkIterator::seek_offset(uint32_t offset) {
stack.clear(); stack.clear();
petal = nullptr; petal = nullptr;
petal_offset = 0; petal_offset = 0;
global_offset = 0;
if (!root || offset >= root->length) if (!root || offset >= root->length)
return; return;
uint32_t target = offset; uint32_t target = offset;
Shard *curr = root; Shard *curr = root;
while (curr) { while (curr) {
if (curr->kind == Shard::ShardKind::Petal) { if (curr->kind == Shard::ShardKind::Petal) {
petal = (Petal *)curr; petal = (Petal *)curr;
@@ -34,28 +32,87 @@ void ChunkIterator::seek(uint32_t offset) {
curr = b->left; curr = b->left;
} else { } else {
target -= left_len; target -= left_len;
global_offset += b->left->length;
curr = b->right; curr = b->right;
} }
} }
} }
} }
void ChunkIterator::seek_line(uint32_t line) {
stack.clear();
petal = nullptr;
petal_offset = 0;
global_offset = 0;
if (!root || line > root->lines)
return;
Shard *curr = root;
while (curr) {
if (curr->kind == Shard::ShardKind::Petal) {
petal = (Petal *)curr;
const char *text = nullptr;
uint32_t remaining = 0;
uint32_t offset = 0;
while (line) {
if (!text) {
uint32_t got = 0;
text = petal->source->read(petal->pos + offset, &got);
remaining = std::min(got, petal->length - offset);
}
const char *c = (const char *)memchr(text, '\n', remaining);
if (!c) {
offset += remaining;
text = nullptr;
continue;
}
remaining -= (c - text) + 1;
offset += (c - text) + 1;
text = c + 1;
line--;
}
petal_offset = offset;
return;
} else {
auto *b = (Branch *)curr;
uint32_t left_lines = b->left->lines;
if (line <= left_lines) {
stack.push_back(b->right);
curr = b->left;
} else {
line -= left_lines;
global_offset += b->left->length;
curr = b->right;
}
}
}
}
uint32_t ChunkIterator::byte_offset() {
return global_offset + petal_offset;
}
bool ChunkIterator::next(const char **data, uint32_t *out_len) { bool ChunkIterator::next(const char **data, uint32_t *out_len) {
while (true) { while (true) {
if (petal && petal_offset < petal->length) { if (petal) {
uint32_t remaining = petal->length - petal_offset; if (petal_offset < petal->length) {
uint32_t read_pos = petal->pos + petal_offset; uint32_t remaining = petal->length - petal_offset;
uint32_t got = 0; uint32_t read_pos = petal->pos + petal_offset;
const char *chunk = petal->source->read(read_pos, &got); uint32_t got = 0;
if (got == 0) { const char *chunk = petal->source->read(read_pos, &got);
petal = nullptr; if (got == 0) {
continue; petal_offset = petal->length;
petal = nullptr;
continue;
}
uint32_t take = std::min(got, remaining);
*data = chunk;
*out_len = take;
petal_offset += take;
return true;
} }
uint32_t take = std::min(got, remaining); global_offset += petal->length;
*data = chunk; petal = nullptr;
*out_len = take; petal_offset = 0;
petal_offset += take;
return true;
} }
if (stack.empty()) if (stack.empty())
return false; return false;
+12 -5
View File
@@ -1,12 +1,12 @@
#include "vase/iterators/line.h" #include "vase/iterators/line.h"
LineIterator::LineIterator(Shard *r, uint32_t start_line) LineIterator::LineIterator(Shard *r, uint32_t start_line) : it(r) {
: offset(offset_of(r, start_line, 0)), it(r, offset) {} it.seek_line(start_line);
}
LineIterator::~LineIterator() {};
bool LineIterator::next(std::string &line) { bool LineIterator::next(std::string &line) {
line.clear(); line.clear();
line_offset = it.byte_offset() - remaining;
while (true) { while (true) {
if (remaining == 0) { if (remaining == 0) {
uint32_t got; uint32_t got;
@@ -14,12 +14,15 @@ bool LineIterator::next(std::string &line) {
eof = true; eof = true;
break; break;
} }
line_offset = it.byte_offset() - remaining;
remaining = got; remaining = got;
} }
const char *nl = (const char *)memchr(cursor, '\n', remaining); const char *nl = (const char *)memchr(cursor, '\n', remaining);
if (nl) { if (nl) {
const char *end = nl; const char *end = nl;
size_t len = end - cursor; uint32_t len = nl - cursor;
if (len && cursor[len - 1] == '\r')
--len;
line.append(cursor, len); line.append(cursor, len);
cursor = end + 1; cursor = end + 1;
remaining -= (uint32_t)(len + 1); remaining -= (uint32_t)(len + 1);
@@ -31,3 +34,7 @@ bool LineIterator::next(std::string &line) {
} }
return !line.empty(); return !line.empty();
} }
uint32_t LineIterator::byte_offset() {
return line_offset;
}
+33 -5
View File
@@ -9,6 +9,8 @@ std::vector<RegexMatch> regex_search(
bool global = false; bool global = false;
uint32_t flags = PCRE2_MULTILINE; uint32_t flags = PCRE2_MULTILINE;
const char *dot = "(?:(?!\\n)\\X)";
for (char c : options) { for (char c : options) {
switch (c) { switch (c) {
case 'g': case 'g':
@@ -18,7 +20,7 @@ std::vector<RegexMatch> regex_search(
flags |= PCRE2_CASELESS; flags |= PCRE2_CASELESS;
break; break;
case 's': case 's':
flags |= PCRE2_DOTALL; dot = "(?:\\X)";
break; break;
case 'x': case 'x':
flags |= PCRE2_EXTENDED; flags |= PCRE2_EXTENDED;
@@ -40,11 +42,36 @@ std::vector<RegexMatch> regex_search(
int errornumber; int errornumber;
PCRE2_SIZE erroroffset; PCRE2_SIZE erroroffset;
PCRE2_SPTR pattern = (PCRE2_SPTR)pattern_str.data(); std::string out;
out.reserve(pattern_str.size() * 2);
bool escaped = false;
for (char c : pattern_str) {
// TODO: also dont switch out in [.] style groups.
if (escaped) {
out += c;
escaped = false;
continue;
}
if (c == '\\') {
out += c;
escaped = true;
continue;
}
if (c == '.') {
out += dot;
continue;
}
out += c;
}
pcre2_code *re = pcre2_compile( pcre2_code *re = pcre2_compile(
pattern, (PCRE2_SPTR)out.data(),
pattern_str.size(), out.size(),
flags, flags,
&errornumber, &errornumber,
&erroroffset, &erroroffset,
@@ -56,7 +83,8 @@ std::vector<RegexMatch> regex_search(
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL); pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
ChunkIterator it(root, start_offset); ChunkIterator it(root);
it.seek_offset(start_offset);
const char *data; const char *data;
uint32_t length; uint32_t length;
+78 -60
View File
@@ -1,5 +1,21 @@
#include "vase/shard.h" #include "vase/shard.h"
void Shard::retain(Shard *n) {
n->refs++;
};
void Shard::release(Shard *n) {
if (!n || --n->refs > 0)
return;
if (n->kind == Shard::ShardKind::Branch) {
release(((Branch *)n)->left);
release(((Branch *)n)->right);
delete (Branch *)n;
} else {
delete (Petal *)n;
}
}
int height(Shard *n) { int height(Shard *n) {
return n ? n->height : 0; return n ? n->height : 0;
} }
@@ -123,15 +139,37 @@ std::pair<Shard *, Shard *> split_shard(Shard *n, uint32_t offset) {
} }
} else { } else {
Petal *p = (Petal *)n; Petal *p = (Petal *)n;
uint32_t count[2]{0};
uint32_t read_offset = 0;
while (read_offset < p->length) {
uint32_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;
while (cursor < end) {
const char *nl = (const char *)memchr(cursor, '\n', end - cursor);
if (!nl) {
cursor = end;
break;
}
uint32_t nl_pos = read_offset + (uint32_t)(nl - c);
if (nl_pos < offset)
count[0]++;
else
count[1]++;
cursor = nl + 1;
}
read_offset += (uint32_t)(cursor - c);
}
auto left = new Petal( auto left = new Petal(
offset, offset,
p->source->count_lines(p->pos, offset), count[0],
p->source, p->source,
p->pos p->pos
); );
auto right = new Petal( auto right = new Petal(
p->length - offset, p->length - offset,
p->source->count_lines(p->pos + offset, p->length - offset), count[1],
p->source, p->source,
p->pos + offset p->pos + offset
); );
@@ -157,7 +195,7 @@ Shard *merge_leaves(Shard *a, Shard *b) {
Shard *append_leaf(Shard *root, Shard *leaf) { Shard *append_leaf(Shard *root, Shard *leaf) {
if (!root) if (!root)
return leaf; return leaf;
if (root->kind == Shard::ShardKind::Petal) if (root->kind == Shard::ShardKind::Petal && root->length < 32 * 1024)
return merge_leaves(root, leaf); return merge_leaves(root, leaf);
Branch *b = (Branch *)root; Branch *b = (Branch *)root;
auto new_right = append_leaf(b->right, leaf); auto new_right = append_leaf(b->right, leaf);
@@ -170,67 +208,47 @@ Shard *concat_shard(Shard *left, Shard *right) {
return merge(left, right); return merge(left, right);
} }
uint32_t offset_of(Shard *s, uint32_t line_number, uint32_t col) { Shard *build_balanced(Shard **pieces, uint32_t lo, uint32_t hi) {
if (line_number == 0) if (hi - lo == 1)
return col; return pieces[lo];
size_t mid = lo + (hi - lo) / 2;
Shard *left = build_balanced(pieces, lo, mid);
Shard *right = build_balanced(pieces, mid, hi);
Shard *node = new Branch(left, right);
Shard::release(left);
Shard::release(right);
return node;
}
uint32_t nth = line_number; Shard *create_shards(Buffer *o) {
uint32_t base_offset = 0; constexpr uint32_t PETAL_SIZE = 32 * 1024;
std::vector<Shard *> pieces;
uint32_t pos = 0;
const uint32_t total = o->length();
pieces.reserve((total + PETAL_SIZE - 1) / PETAL_SIZE);
while (s->kind == Shard::ShardKind::Branch) { while (pos < total) {
auto *b = (Branch *)s; uint32_t len = 0;
if (nth <= b->left->lines) { const char *data = o->read(pos, &len);
s = b->left; uint32_t take = std::min(len, PETAL_SIZE);
} else { uint32_t lines = 0;
nth -= b->left->lines; const char *p = data;
base_offset += b->left->length; const char *end = data + take;
s = b->right; while (p < end) {
const void *nl = memchr(p, '\n', end - p);
if (!nl)
break;
lines++;
p = (const char *)nl + 1;
} }
pieces.push_back(new Petal(take, lines, o, pos));
pos += take;
} }
auto *petal = (Petal *)s; if (pieces.empty())
uint32_t nl_pos = petal->source->nth_newline(petal->pos, nth); return nullptr;
uint32_t offset_in_petal = (nl_pos - petal->pos) + 1; if (pieces.size() == 1)
return base_offset + offset_in_petal + col; return pieces[0];
}
void print_shard(const Shard *shard, int depth) { return build_balanced(pieces.data(), 0, pieces.size());
if (!shard) {
std::cout << std::string(depth * 2, ' ') << "<null>\n";
return;
}
std::string indent(depth * 2, ' ');
std::cout << indent;
if (shard->kind == Shard::ShardKind::Branch) {
auto *branch = (const Branch *)shard;
std::cout
<< "Branch"
<< " @" << shard
<< " len=" << shard->length
<< " lines=" << shard->lines
<< " height=" << (int)shard->height
<< " refs=" << shard->refs.load()
<< "\n";
std::cout << indent << "├─ left:\n";
print_shard(branch->left, depth + 2);
std::cout << indent << "└─ right:\n";
print_shard(branch->right, depth + 2);
} else {
auto *petal = (const Petal *)(shard);
std::cout
<< "Petal"
<< " @" << shard
<< " len=" << shard->length
<< " lines=" << shard->lines
<< " refs=" << shard->refs.load()
<< " source=@" << petal->source
<< " pos=" << petal->pos
<< "\n";
}
} }
+75 -26
View File
@@ -1,9 +1,11 @@
#include "vase/vase.h" #include "vase/vase.h"
#include "utils/utils.h"
#include "vase/iterators/line.h"
#include "vase/search.h" #include "vase/search.h"
Vase::Vase(char *data, uint32_t length) : original(data, length), append() { Vase::Vase(char *data, uint32_t length)
root = new Petal(length, original.newlines.size(), &original, 0); : original(data, length), append(),
} root(create_shards(&original)) {}
Vase::~Vase() { Vase::~Vase() {
Shard::release(root); Shard::release(root);
@@ -19,10 +21,15 @@ std::string Vase::to_string() {
return out; return out;
} }
void Vase::type(uint32_t offset, char key) { void Vase::input(Point *point, char key) {
if (key == '\n')
*point = {point->row + 1, 0};
else
point->col++;
uint32_t pos = append.key(key); uint32_t pos = append.key(key);
// limit petal size to 32KiB here later.
Shard *inserted = new Petal(1, key == '\n', &append, pos); Shard *inserted = new Petal(1, key == '\n', &append, pos);
auto [left, right] = split_shard(root, offset); auto [left, right] = split_shard(root, offset_of(*point));
Shard *left2 = append_leaf(left, inserted); Shard *left2 = append_leaf(left, inserted);
Shard *new_root = concat_shard(left2, right); Shard *new_root = concat_shard(left2, right);
Shard::release(left); Shard::release(left);
@@ -33,9 +40,33 @@ void Vase::type(uint32_t offset, char key) {
root = new_root; root = new_root;
} }
void Vase::insert(uint32_t offset, const char *data, uint32_t len) { void Vase::insert(Point *point, const char *data, uint32_t len) {
if (len == 0)
return;
uint32_t offset = offset_of(*point);
uint32_t pos = append.append(data, len);
uint32_t lines = 0; uint32_t lines = 0;
uint32_t pos = append.append(data, len, &lines); 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;
}
uint32_t col = 0;
uint32_t remaining = end - last_line;
while (remaining) {
uint32_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); Shard *inserted = new Petal(len, lines, &append, pos);
auto [left, right] = split_shard(root, offset); auto [left, right] = split_shard(root, offset);
Shard *left2 = append_leaf(left, inserted); Shard *left2 = append_leaf(left, inserted);
@@ -48,16 +79,18 @@ void Vase::insert(uint32_t offset, const char *data, uint32_t len) {
root = new_root; root = new_root;
} }
void Vase::erase(uint32_t cursor, int64_t amount) { void Vase::erase(Point *point, int64_t amount) {
// wrong: havta make amount be in clusters not bytes
uint32_t offset = offset_of(*point);
if (amount == 0) if (amount == 0)
return; return;
uint32_t start; uint32_t start;
uint32_t count; uint32_t count;
if (amount < 0) { if (amount < 0) {
count = std::min<uint32_t>(-amount, cursor); count = std::min<uint32_t>(-amount, offset);
start = cursor - count; start = offset - count;
} else { } else {
start = cursor; start = offset;
count = amount; count = amount;
} }
auto [a, b] = split_shard(root, start); auto [a, b] = split_shard(root, start);
@@ -91,6 +124,34 @@ void Vase::flatten(Shard *s, std::string &out) {
} }
} }
uint32_t Vase::offset_of(Point point) {
LineIterator it(root, point.row);
std::string line;
uint32_t offset = 0;
if (it.next(line)) {
const char *ptr = line.data();
uint32_t remaining = line.length();
while (point.col && remaining) {
uint32_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;
}
bool Vase::jump(Point *, int64_t) {
// todo.
return false;
}
bool Vase::clamp(Point *) {
// todo.
return false;
}
struct ReplacePart { struct ReplacePart {
enum struct PartType { enum struct PartType {
FullMatch, FullMatch,
@@ -107,7 +168,7 @@ std::vector<ReplacePart> parse_replace(AppendBuffer &buf, std::string_view s) {
auto flush_constant = [&]() { auto flush_constant = [&]() {
if (!constant.empty()) { if (!constant.empty()) {
uint32_t lines = 0; uint32_t lines = 0;
uint32_t pos = buf.append(constant.data(), (uint32_t)constant.size(), &lines); uint32_t pos = buf.append(constant.data(), (uint32_t)constant.size());
parts.push_back( parts.push_back(
ReplacePart{ ReplacePart{
.type = ReplacePart::PartType::Constant, .type = ReplacePart::PartType::Constant,
@@ -155,24 +216,12 @@ std::vector<ReplacePart> parse_replace(AppendBuffer &buf, std::string_view s) {
return parts; return parts;
} }
Shard *build_balanced(Shard **pieces, size_t lo, size_t hi) {
if (hi - lo == 1)
return pieces[lo];
size_t mid = lo + (hi - lo) / 2;
Shard *left = build_balanced(pieces, lo, mid);
Shard *right = build_balanced(pieces, mid, hi);
Shard *node = new Branch(left, right);
Shard::release(left);
Shard::release(right);
return node;
}
void Vase::regex_search_replace( void Vase::regex_search_replace(
std::string_view pattern, std::string_view pattern,
uint32_t start_offset, uint32_t end_offset, Point start, Point end,
std::string_view replace, std::string_view options std::string_view replace, std::string_view options
) { ) {
std::vector<RegexMatch> matches = regex_search(root, pattern, start_offset, end_offset, options); std::vector<RegexMatch> matches = regex_search(root, pattern, offset_of(start), offset_of(end), options);
if (matches.empty()) if (matches.empty())
return; return;