diff --git a/Makefile b/Makefile index 29ce59f..43ee401 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ UNICODE_OBJ := $(patsubst libs/unicode_width/%.c,$(OBJ_DIR)/unicode_width/%.o,$( LIBS := $(PCRE_LIBS) $(LIBGRAPHEME_LIBS) $(MRUBY_LIBS) -SRC := $(wildcard $(SRC_DIR)/**/*.cc) $(wildcard $(SRC_DIR)/*.cc) +SRC := $(shell find $(SRC_DIR) -type f -name '*.cc') OBJ_DEBUG := $(patsubst $(SRC_DIR)/%.cc,$(OBJ_DIR)/debug/%.o,$(SRC)) OBJ_RELEASE := $(patsubst $(SRC_DIR)/%.cc,$(OBJ_DIR)/release/%.o,$(SRC)) diff --git a/flake.nix b/flake.nix index 3dda98f..96bde3c 100644 --- a/flake.nix +++ b/flake.nix @@ -58,6 +58,7 @@ gnumake xxd bash + findutils ]; buildInputs = with pkgs; [ @@ -100,6 +101,7 @@ hostPkgs.xxd hostPkgs.gdb hostPkgs.binutils + hostPkgs.findutils pkgs.libgrapheme pkgs.pcre2 diff --git a/include/io/file.h b/include/io/file.h new file mode 100644 index 0000000..eb30a4e --- /dev/null +++ b/include/io/file.h @@ -0,0 +1,40 @@ +#pragma once + +#include "pch.h" + +inline int read_file(const char *path, char **text, uint32_t *len) { + FILE *f = fopen(path, "rb"); + if (!f) + return 0; + + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (size < 0 || size > UINT32_MAX) { + fclose(f); + return 0; + } + + *len = (uint32_t)size; + + *text = (char *)malloc(*len + 1); + if (!*text) { + fclose(f); + return 0; + } + + size_t read = fread(*text, 1, *len, f); + fclose(f); + + if (read != *len) { + free(*text); + *text = NULL; + *len = 0; + return 0; + } + + (*text)[*len] = '\0'; // handy if treating it as a C string + + return 1; +} diff --git a/include/pch.h b/include/pch.h index 1741e9a..a2ca4c8 100644 --- a/include/pch.h +++ b/include/pch.h @@ -30,6 +30,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/include/vase/append_buffer.h b/include/vase/buffer/append.h similarity index 69% rename from include/vase/append_buffer.h rename to include/vase/buffer/append.h index 125221d..441209a 100644 --- a/include/vase/append_buffer.h +++ b/include/vase/buffer/append.h @@ -1,9 +1,12 @@ #pragma once +#include "buffer.h" #include "pch.h" +#include -struct AppendBuffer { +struct AppendBuffer : Buffer { static constexpr uint32_t CHUNK_SIZE = 1 << 16; + using TChunk = char[CHUNK_SIZE]; std::vector buf; TChunk *t_current; @@ -22,9 +25,14 @@ struct AppendBuffer { inline void key(char c); void append(const char *text, uint32_t length); + const char *read(uint32_t pos, uint32_t *out_len); + uint32_t count_lines(uint32_t pos, uint32_t length); + private: 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 void _append(const char *text, uint32_t length); }; diff --git a/include/vase/buffer/buffer.h b/include/vase/buffer/buffer.h new file mode 100644 index 0000000..57ef4f6 --- /dev/null +++ b/include/vase/buffer/buffer.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pch.h" + +struct Buffer { + 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 ~Buffer() = default; +}; diff --git a/include/vase/original_buffer.h b/include/vase/buffer/original.h similarity index 76% rename from include/vase/original_buffer.h rename to include/vase/buffer/original.h index 6e8adfd..da55d86 100644 --- a/include/vase/original_buffer.h +++ b/include/vase/buffer/original.h @@ -1,8 +1,9 @@ #pragma once +#include "buffer.h" #include "pch.h" -struct OriginalBuffer { +struct OriginalBuffer : Buffer { char *buf; uint32_t length; std::vector newlines; @@ -23,4 +24,7 @@ struct OriginalBuffer { ~OriginalBuffer() { free(buf); } + + 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/shard.h b/include/vase/shard.h index 2f054e6..bcbd400 100644 --- a/include/vase/shard.h +++ b/include/vase/shard.h @@ -1,5 +1,6 @@ #pragma once +#include "buffer/buffer.h" #include "pch.h" struct Shard { @@ -14,7 +15,7 @@ struct Shard { std::atomic_uint32_t refs; Shard(ShardKind kind, uint32_t length, uint32_t lines) - : kind(kind), length(length), lines(lines) {}; + : kind(kind), length(length), lines(lines), refs(0) {}; }; struct ShardPtr { @@ -53,17 +54,18 @@ struct Branch : Shard { Branch(Shard *l, Shard *r) : Shard(ShardKind::Branch, l->length + r->length, l->lines + r->lines), - left(ShardPtr(l)), right(ShardPtr(r)) {}; + left(l), right(r) {}; }; struct Petal : Shard { - enum struct Kind { - Original, - Append - } source; + Buffer *source; uint32_t pos; - Petal(uint32_t length, uint32_t lines, Kind source, uint32_t pos) + Petal(uint32_t length, uint32_t lines, Buffer *source, uint32_t pos) : Shard(ShardKind::Petal, length, lines), source(source), pos(pos) {}; }; + +std::pair split(Shard *n, uint32_t offset); + +void print_shard(const Shard *shard, int depth = 0); diff --git a/include/vase/vase.h b/include/vase/vase.h index ba2e90b..a493668 100644 --- a/include/vase/vase.h +++ b/include/vase/vase.h @@ -1,7 +1,7 @@ #pragma once -#include "append_buffer.h" -#include "original_buffer.h" +#include "buffer/append.h" +#include "buffer/original.h" #include "pch.h" #include "shard.h" @@ -21,7 +21,7 @@ struct Vase { root = new Petal( length, original.newlines.size(), - Petal::Kind::Original, + &original, 0 ); } diff --git a/src/main.cc b/src/main.cc index 6af9fea..ed2b851 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,19 @@ +#include "io/file.h" #include "pch.h" +#include "vase/shard.h" +#include "vase/vase.h" int main() { + char *text; + uint32_t len; + + int s = read_file("./flake.nix", &text, &len); + if (!s) + return 1; + + Vase vase = Vase(text, len); + + print_shard(vase.root.ptr); + return 0; } diff --git a/src/vase/append_buffer.cc b/src/vase/append_buffer.cc deleted file mode 100644 index 6fd9477..0000000 --- a/src/vase/append_buffer.cc +++ /dev/null @@ -1,71 +0,0 @@ -#include "vase/append_buffer.h" - -AppendBuffer::AppendBuffer() { - new_text_chunk(); - new_line_chunk(); -} - -AppendBuffer::~AppendBuffer() { - for (auto p : buf) - free(p); - for (auto p : newlines) - free(p); -} - -inline void AppendBuffer::key(char c) { - if (t_offset == CHUNK_SIZE) - new_text_chunk(); - (*t_current)[t_offset++] = c; - if (c == '\n') { - if (l_offset == CHUNK_SIZE) - new_line_chunk(); - (*l_current)[l_offset++] = current_offset; - } - ++current_offset; -} - -void AppendBuffer::append(const char *text, uint32_t length) { - while (length) { - uint32_t space = CHUNK_SIZE - t_offset; - if (space == 0) { - new_text_chunk(); - space = CHUNK_SIZE; - } - uint32_t copy = length < space ? length : space; - _append(text, copy); - text += copy; - length -= copy; - } -} - -inline void AppendBuffer::new_text_chunk() { - t_current = (TChunk *)malloc(sizeof(TChunk)); - buf.push_back(t_current); - t_offset = 0; -} - -inline void AppendBuffer::new_line_chunk() { - l_current = (LChunk *)malloc(sizeof(LChunk)); - newlines.push_back(l_current); - l_offset = 0; -} - -inline void AppendBuffer::_append(const char *text, uint32_t length) { - memcpy((*t_current) + t_offset, text, length); - - const char *p = text; - const char *end = text + length; - - 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; -} diff --git a/src/vase/buffer/append.cc b/src/vase/buffer/append.cc new file mode 100644 index 0000000..df4efb8 --- /dev/null +++ b/src/vase/buffer/append.cc @@ -0,0 +1,148 @@ +#include "vase/buffer/append.h" +#include + +AppendBuffer::AppendBuffer() { + new_text_chunk(); + new_line_chunk(); +} + +AppendBuffer::~AppendBuffer() { + for (auto p : buf) + free(p); + for (auto p : newlines) + free(p); +} + +inline void AppendBuffer::key(char c) { + if (t_offset == CHUNK_SIZE) + new_text_chunk(); + (*t_current)[t_offset++] = c; + if (c == '\n') { + if (l_offset == CHUNK_SIZE) + new_line_chunk(); + (*l_current)[l_offset++] = current_offset; + } + ++current_offset; +} + +void AppendBuffer::append(const char *text, uint32_t length) { + while (length) { + uint32_t space = CHUNK_SIZE - t_offset; + if (space == 0) { + new_text_chunk(); + space = CHUNK_SIZE; + } + uint32_t copy = length < space ? length : space; + _append(text, copy); + text += copy; + length -= copy; + } +} + +const char *AppendBuffer::read(uint32_t pos, uint32_t *out_len) { + if (pos >= current_offset) + return nullptr; + uint32_t local_offset = pos % CHUNK_SIZE; + if (out_len) { + uint32_t remaining = current_offset - pos; + uint32_t until_chunk_end = CHUNK_SIZE - local_offset; + *out_len = std::min(remaining, until_chunk_end); + } + return &(*buf[pos / CHUNK_SIZE])[local_offset]; +} + +uint32_t AppendBuffer::find_newline(uint32_t target, uint32_t low, uint32_t high) { + if (low >= high) + 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; +} + +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() { + t_current = (TChunk *)malloc(sizeof(TChunk)); + buf.push_back(t_current); + t_offset = 0; +} + +inline void AppendBuffer::new_line_chunk() { + l_current = (LChunk *)malloc(sizeof(LChunk)); + newlines.push_back(l_current); + l_offset = 0; +} + +inline void AppendBuffer::_append(const char *text, uint32_t length) { + memcpy((*t_current) + t_offset, text, length); + + const char *p = text; + const char *end = text + length; + + 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; +} diff --git a/src/vase/buffer/original.cc b/src/vase/buffer/original.cc new file mode 100644 index 0000000..c193300 --- /dev/null +++ b/src/vase/buffer/original.cc @@ -0,0 +1,24 @@ +#include "vase/buffer/original.h" + +const char *OriginalBuffer::read(uint32_t pos, uint32_t *out_len) { + if (pos >= length) + return nullptr; + *out_len = length - pos; + return buf + pos; +} + +uint32_t OriginalBuffer::count_lines(uint32_t pos, uint32_t length) { + auto start = std::lower_bound( + newlines.begin(), + newlines.end(), + pos + ); + + auto end = std::lower_bound( + start, + newlines.end(), + pos + length + ); + + return (uint32_t)(end - start); +} diff --git a/src/vase/shard.cc b/src/vase/shard.cc new file mode 100644 index 0000000..29c9471 --- /dev/null +++ b/src/vase/shard.cc @@ -0,0 +1,103 @@ +#include "vase/shard.h" + +std::pair split(Shard *n, uint32_t offset) { + if (!n) + return {nullptr, nullptr}; + + if (offset == 0) + return {nullptr, ShardPtr(n)}; + + if (offset == n->length) + return {ShardPtr(n), nullptr}; + + if (n->kind == Shard::ShardKind::Branch) { + Branch *b = static_cast(n); + + if (offset < b->left.ptr->length) { + auto [a, b2] = split(b->left.ptr, offset); + + return { + a, + ShardPtr(new Branch( + b2.ptr, + b->right.ptr + )) + }; + } else { + auto [a, b2] = split( + b->right.ptr, + offset - b->left.ptr->length + ); + + return { + ShardPtr(new Branch( + b->left.ptr, + a.ptr + )), + b2 + }; + } + } + + Petal *p = static_cast(n); + + auto left = new Petal( + offset, + p->source->count_lines(0, offset), + p->source, + p->pos + ); + + auto right = new Petal( + p->length - offset, + p->source->count_lines(offset, p->length), + p->source, + p->pos + offset + ); + + return { + ShardPtr(left), + ShardPtr(right) + }; +} + +void print_shard(const Shard *shard, int depth) { + if (!shard) { + std::cout << std::string(depth * 2, ' ') << "\n"; + return; + } + + std::string indent(depth * 2, ' '); + + std::cout << indent; + + if (shard->kind == Shard::ShardKind::Branch) { + auto *branch = static_cast(shard); + + std::cout + << "Branch" + << " @" << shard + << " len=" << shard->length + << " lines=" << shard->lines + << " refs=" << shard->refs.load() + << "\n"; + + std::cout << indent << "├─ left:\n"; + print_shard(branch->left.ptr, depth + 2); + + std::cout << indent << "└─ right:\n"; + print_shard(branch->right.ptr, depth + 2); + } else { + auto *petal = static_cast(shard); + + std::cout + << "Petal" + << " @" << shard + << " len=" << shard->length + << " lines=" << shard->lines + << " refs=" << shard->refs.load() + << " source=@" << petal->source + << " pos=" << petal->pos + << "\n"; + } +}