initial commit
Zig Project Action / Lint, Spell-check and test zig project (push) Has been cancelled
Zig Project Action / Lint, Spell-check and test zig project (push) Has been cancelled
based on sources of https://github.com/noahbald/noe which are MIT-licensed
This commit is contained in:
+260
@@ -0,0 +1,260 @@
|
||||
//! A collection of iterator types.
|
||||
const std = @import("std");
|
||||
|
||||
// --- OPAQUE ---
|
||||
pub fn SliceIter(T: type) type {
|
||||
return struct {
|
||||
buffer: []const T,
|
||||
i: usize = 0,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Item = *const T;
|
||||
|
||||
pub fn next(self: *Self) ?Item {
|
||||
if (self.peek()) |result| {
|
||||
self.i += 1;
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(self: *const Self) ?Item {
|
||||
if (self.i < self.buffer.len) {
|
||||
return &self.buffer[self.i];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(self: *const Self) usize {
|
||||
return if (self.i < self.buffer.len) self.buffer.len - self.i else 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ParallelSliceIter(T: type) type {
|
||||
return struct {
|
||||
inner: SliceIter(T),
|
||||
lock: std.Thread.Mutex = .{},
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn parNext(self: *Self) ?.{ T, usize } {
|
||||
self.lock.lock();
|
||||
const i = self.inner.i;
|
||||
const result = self.inner.next();
|
||||
self.lock.unlock();
|
||||
return .{ result, i };
|
||||
}
|
||||
|
||||
pub fn len(self: *const Self) usize {
|
||||
return self.inner.len();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// --- TRANSPARENT ---
|
||||
pub fn Map(T: type, R: type, C: type) type {
|
||||
return struct {
|
||||
inner: T,
|
||||
context: C,
|
||||
f: fn (T, C) R,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn next(self: *Self) ?R {
|
||||
return self.f(self.inner.next().?, self.context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Flatten(T: type) type {
|
||||
return struct {
|
||||
inner: T,
|
||||
current: ?T.Item = null,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn next(self: *Self) ?T.Item.Item {
|
||||
if (self.current) |current| {
|
||||
if (current) |item| {
|
||||
return item;
|
||||
} else {
|
||||
self.current = null;
|
||||
return self.next();
|
||||
}
|
||||
} else {
|
||||
if (self.inner.next()) |current| {
|
||||
self.current = current;
|
||||
return self.next();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Take(T: type) type {
|
||||
return struct {
|
||||
inner: T,
|
||||
remainder: usize,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn next(self: *Self) ?T.Item {
|
||||
if (self.remainder == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return self.inner.next();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ParallelMap(T: type, R: type, C: type) type {
|
||||
return struct {
|
||||
inner: T,
|
||||
context: C,
|
||||
f: fn (T, C) R,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn parNext(self: *Self) ?.{ T, usize } {
|
||||
const inner = self.inner.parNext().?;
|
||||
return .{ self.f(inner[0], self.context), inner[1] };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// --- BASE ---
|
||||
pub fn Iter(T: type) type {
|
||||
return struct {
|
||||
impl: T,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
/// Advances the iterator and returns the next value.
|
||||
///
|
||||
/// Returns `null` once the iterator is finished.
|
||||
pub fn next(self: *Self) ?T.Item {
|
||||
return self.impl.next();
|
||||
}
|
||||
|
||||
/// Advances the iterator by a number of steps, usually without
|
||||
/// processing the intermediate values.
|
||||
pub fn skip(self: *Self) void {
|
||||
self.impl.skip();
|
||||
}
|
||||
|
||||
/// Returns the exact remaining length of the iterator.
|
||||
///
|
||||
/// This method is optional by implementors, and may result in a compile error
|
||||
/// if not provided by the implementor.
|
||||
pub fn len(self: *const Self) usize {
|
||||
return self.impl.len();
|
||||
}
|
||||
|
||||
/// For an iterator of an iterator, returns an iterator over the
|
||||
/// nested items.
|
||||
pub fn flatten(self: Self) Iter(Flatten(T)) {
|
||||
return .{ .impl = .{ .inner = self.impl } };
|
||||
}
|
||||
|
||||
/// Takes a function and creates an iterator that calls the function on each element.
|
||||
///
|
||||
/// Since closures are unavailable in zig, `mapScope` can be used to provide a scope
|
||||
/// or context that's passed to the function.
|
||||
pub fn map(self: Self, R: type, f: fn (T, void) R) Map(Self, R, void) {
|
||||
return self.mapScope(self, R, f, void{});
|
||||
}
|
||||
|
||||
/// Takes a function and context and creates an iterator that calls the function on each element.
|
||||
///
|
||||
/// The same context will be passed along with each item to the function.
|
||||
pub fn mapScope(self: Self, R: type, C: type, f: fn (T, C) R, context: C) Map(Self, R, C) {
|
||||
return .{ .inner = self, .f = f, .context = context };
|
||||
}
|
||||
|
||||
/// Returns an iterator that will continue to iterate until `n` items
|
||||
/// have been yielded.
|
||||
pub fn take(self: Self, n: usize) Iter(Take(T)) {
|
||||
return .{ .impl = .{ .inner = self.impl, .remainder = n } };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ParallelIter(T: type) type {
|
||||
return struct {
|
||||
impl: T,
|
||||
pool: []std.Thread = undefined,
|
||||
poolSize: usize,
|
||||
sink: []T = undefined,
|
||||
sinkIndex: usize = 0,
|
||||
sinkIndexLock: std.Thread.Mutex = .{},
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(impl: T) Self {
|
||||
const cpuCount = try std.Thread.getCpuCount() catch return .{
|
||||
.impl = impl,
|
||||
// If cpu-count fails, assume single threaded
|
||||
.poolSize = 0,
|
||||
};
|
||||
const poolSize = (impl.len() + cpuCount - 1) / cpuCount;
|
||||
|
||||
return .{
|
||||
.impl = impl,
|
||||
.poolSize = poolSize,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, gpa: std.mem.Allocator) void {
|
||||
gpa.free(self.pool);
|
||||
gpa.free(self.sink);
|
||||
}
|
||||
|
||||
/// Spawn threads to process each element of the iterator
|
||||
pub fn fork(self: *Self, gpa: std.mem.Allocator) void {
|
||||
self.pool = try gpa.alloc(std.Thread, self.poolSize);
|
||||
self.sink = try gpa.alloc(T, self.impl.len());
|
||||
|
||||
if (self.poolSize == 0) {
|
||||
return; // Assume threads are unavailable, complete syncronously
|
||||
} else {
|
||||
for (self.pool) |*thread| {
|
||||
thread.* = try std.Thread.spawn(.{}, threadHandle, .{&self});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join(self: *Self) []T {
|
||||
if (self.pool.len == 0) {
|
||||
// Assume threads are unavailable, complete syncronously
|
||||
while (true) {
|
||||
self.handle() orelse break;
|
||||
}
|
||||
} else {
|
||||
for (self.pool) |thread| {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
return self.sink;
|
||||
}
|
||||
|
||||
fn threadHandle(self: *Self) void {
|
||||
for (0..self.poolSize) |_| {
|
||||
self.handle() orelse break;
|
||||
}
|
||||
}
|
||||
fn handle(self: *Self) ?void {
|
||||
var item: T = undefined;
|
||||
var i: usize = undefined;
|
||||
item, i = self.impl.parNext().?;
|
||||
self.sink[i] = item;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
pub const Ordering = enum(u2) {
|
||||
less = 0,
|
||||
equal = 1,
|
||||
greater = 2,
|
||||
|
||||
pub fn cmp(a: anytype, b: anytype) Ordering {
|
||||
if (a < b) return .less else if (a == b) return .equal else return .greater;
|
||||
}
|
||||
|
||||
pub fn gte(self: Ordering) bool {
|
||||
return @intFromEnum(self) > 1;
|
||||
}
|
||||
|
||||
pub fn lte(self: Ordering) bool {
|
||||
return @intFromEnum(self) < 1;
|
||||
}
|
||||
|
||||
pub fn is(self: Ordering, other: Ordering) bool {
|
||||
return @intFromEnum(self) == @intFromEnum(other);
|
||||
}
|
||||
};
|
||||
+911
@@ -0,0 +1,911 @@
|
||||
//! A rope is a data-structure that can be efficiently edited, representing a string of u8 bytes.
|
||||
//!
|
||||
//! The rope is represented by a sum-tree which provides efficient extraction and navigation of
|
||||
//! the the tree and it's metadata.
|
||||
const std = @import("std");
|
||||
|
||||
const arrayVec = @import("rope/array-vec.zig");
|
||||
const Chunk = @import("rope/chunk.zig");
|
||||
const Point = @import("rope/point.zig");
|
||||
const sumTree = @import("rope/sum-tree.zig");
|
||||
const iter = @import("iter.zig");
|
||||
|
||||
const Rope = @This();
|
||||
const SumTree = sumTree.SumTree;
|
||||
pub const Tree = SumTree(Chunk);
|
||||
|
||||
const Error = sumTree.Error || arrayVec.Error || std.mem.Allocator.Error;
|
||||
|
||||
/// The sum-tree of the rope's contents, stored as chunks of text.
|
||||
tree: *Tree,
|
||||
gpa: std.mem.Allocator,
|
||||
|
||||
pub fn init(gpa: std.mem.Allocator) std.mem.Allocator.Error!Rope {
|
||||
return .{
|
||||
.tree = try .init(gpa, void{}),
|
||||
.gpa = gpa,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Rope) void {
|
||||
self.tree.deinit(self.gpa);
|
||||
}
|
||||
|
||||
/// Take a reference to a reader and from it, generates a rope containing the reader's output.
|
||||
pub fn read(self: *Rope, reader: *std.Io.Reader) (std.Io.Reader.ShortError || Error)!void {
|
||||
var buffer: [Chunk.MAX_BASE]u8 = undefined;
|
||||
while (true) {
|
||||
const bytes = try reader.readSliceShort(&buffer);
|
||||
try self.push(buffer[0..bytes]);
|
||||
if (bytes < buffer.len) break;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice(self: *const Rope, start: usize, end: usize) Slice {
|
||||
std.debug.assert(start <= end);
|
||||
var c = self.cursor(start);
|
||||
return c.slice(end);
|
||||
}
|
||||
|
||||
pub fn sliceRows(self: *const Rope, start: usize, end: usize) Slice {
|
||||
std.debug.assert(start <= end);
|
||||
const startByte = self.pointToOffset(.init(start, 0));
|
||||
const endByte = self.pointToOffset(.init(end + 1, 0));
|
||||
if (endByte == 0 or startByte == endByte)
|
||||
return self.slice(startByte, startByte);
|
||||
if (endByte == self.len())
|
||||
return self.slice(startByte, endByte);
|
||||
return self.slice(startByte, endByte - 1);
|
||||
}
|
||||
|
||||
pub fn sliceLine(self: *const Rope, line: usize) Slice {
|
||||
return self.sliceRows(line, line);
|
||||
}
|
||||
|
||||
/// Pushes the text to the end of the rope and rebalances the tree.
|
||||
pub fn push(self: *Rope, text: []const u8) Error!void {
|
||||
var offset: usize = 0;
|
||||
while (offset < text.len) {
|
||||
// Split into chunks
|
||||
const chunkSize = @min(Chunk.MAX_BASE, text.len - offset);
|
||||
const chunkText = text[offset .. offset + chunkSize];
|
||||
const chunk: Chunk = try .init(chunkText);
|
||||
offset += chunkSize;
|
||||
|
||||
// Push chunk and handle split
|
||||
if (try self.tree.push(self.gpa, chunk, {})) |right| {
|
||||
const root = try self.gpa.create(Tree);
|
||||
const left = self.tree;
|
||||
|
||||
root.* = .{ .internal = .{
|
||||
.height = left.height() + 1,
|
||||
.summary = left.summary().add(&right.summary(), {}),
|
||||
.childSummaries = .init,
|
||||
.childTrees = .init,
|
||||
} };
|
||||
|
||||
try root.internal.childTrees.push(left);
|
||||
try root.internal.childTrees.push(right);
|
||||
try root.internal.childSummaries.push(left.summary());
|
||||
try root.internal.childSummaries.push(right.summary());
|
||||
|
||||
self.tree = root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutates the rope by inserting the text at the given offset
|
||||
pub fn insert(self: *Rope, offset: usize, text: []const u8) Error!void {
|
||||
if (offset >= self.len()) {
|
||||
return self.push(text);
|
||||
} else if (offset == 0) {
|
||||
var left = try init(self.gpa);
|
||||
errdefer left.deinit();
|
||||
try left.push(text);
|
||||
std.mem.swap(Tree, self.tree, left.tree); // left is now right
|
||||
return;
|
||||
}
|
||||
|
||||
var right = try self.split(offset);
|
||||
var middle = try init(self.gpa);
|
||||
try middle.push(text);
|
||||
|
||||
try self.join(&middle);
|
||||
if (right) |*r| {
|
||||
try self.join(r);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(self: *Rope, offset: usize) Error!void {
|
||||
if (offset >= self.len()) return;
|
||||
|
||||
var right = try self.split(offset);
|
||||
if (right) |*r| {
|
||||
if (r.len() <= 1) return;
|
||||
defer r.deinit();
|
||||
var rest = try r.split(1);
|
||||
if (rest) |*r2| {
|
||||
try self.join(r2);
|
||||
}
|
||||
}
|
||||
self.tree.checkInvariants(void{});
|
||||
}
|
||||
|
||||
/// Splits the rope by mutation at the given offset, popping and returning the remaining rope.
|
||||
pub fn split(self: *Rope, offset: usize) Error!?Rope {
|
||||
self.tree.checkInvariants(void{});
|
||||
|
||||
var right: Rope = .{ .tree = undefined, .gpa = self.gpa };
|
||||
right.tree = try self.splitTree(offset, 0) orelse return null;
|
||||
self.tree.checkInvariants(void{});
|
||||
right.tree.checkInvariants(void{});
|
||||
return right;
|
||||
}
|
||||
fn splitTree(self: *Rope, offset: usize, baseOffset: usize) Error!?*Tree {
|
||||
switch (self.tree.*) {
|
||||
.internal => |*internal| {
|
||||
// Find split boundary
|
||||
var currentOffset = baseOffset;
|
||||
var splitIndex: ?usize = null;
|
||||
|
||||
for (internal.childSummaries.slice(), 0..) |sum, i| {
|
||||
if (currentOffset + sum.len > offset) {
|
||||
splitIndex = i;
|
||||
break;
|
||||
}
|
||||
currentOffset += sum.len;
|
||||
}
|
||||
const i = splitIndex orelse return null;
|
||||
|
||||
// Split boundary point
|
||||
var middle: Rope = .{ .tree = undefined, .gpa = self.gpa };
|
||||
middle.tree = internal.childTrees.slice()[i];
|
||||
const rightStart = try middle.splitTree(offset, currentOffset);
|
||||
internal.childSummaries.sliceMut()[i] = internal.childTrees.slice()[i].summary();
|
||||
const hasRemainingChildren = i + 1 < internal.childTrees.slice().len;
|
||||
const hasRightContent = rightStart != null or hasRemainingChildren;
|
||||
|
||||
if (hasRightContent) {
|
||||
// Build right tree
|
||||
var right = try self.gpa.create(Tree);
|
||||
errdefer right.deinit(self.gpa);
|
||||
right.* = .{ .internal = .{
|
||||
.height = internal.height,
|
||||
.summary = Chunk.Summary.zero(void{}),
|
||||
.childSummaries = .init,
|
||||
.childTrees = .init,
|
||||
} };
|
||||
|
||||
if (rightStart) |tree| {
|
||||
try right.internal.childTrees.push(tree);
|
||||
const sum = tree.summary();
|
||||
try right.internal.childSummaries.push(sum);
|
||||
right.internal.summary = sum;
|
||||
}
|
||||
for (internal.childTrees.slice()[i + 1 ..], internal.childSummaries.slice()[i + 1 ..]) |child, sum| {
|
||||
try right.internal.childTrees.push(child);
|
||||
try right.internal.childSummaries.push(sum);
|
||||
right.internal.summary = right.internal.summary.add(&sum, void{});
|
||||
}
|
||||
|
||||
// Slice left tree
|
||||
if (middle.summary().len == 0) {
|
||||
internal.childTrees.slice()[i].deinit(self.gpa);
|
||||
internal.childTrees.len = i;
|
||||
internal.childSummaries.len = i;
|
||||
} else {
|
||||
internal.childTrees.len = i + 1;
|
||||
internal.childSummaries.len = i + 1;
|
||||
}
|
||||
internal.summary = Chunk.Summary.zero(void{});
|
||||
for (internal.childSummaries.slice()) |sum| {
|
||||
internal.summary = internal.summary.add(&sum, void{});
|
||||
}
|
||||
self.tree.checkInvariants(void{});
|
||||
right.checkInvariants(void{});
|
||||
return right;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
.leaf => |*leaf| {
|
||||
// Find split boundary
|
||||
var currentOffset = baseOffset;
|
||||
var splitIndex: ?usize = null;
|
||||
var splitText: usize = 0;
|
||||
|
||||
for (leaf.items.slice(), 0..) |chunk, i| {
|
||||
if (currentOffset + chunk.text.len > offset) {
|
||||
splitIndex = i;
|
||||
splitText = offset - currentOffset;
|
||||
break;
|
||||
}
|
||||
currentOffset += chunk.text.len;
|
||||
}
|
||||
const i = splitIndex orelse return null;
|
||||
|
||||
// Split boundary point
|
||||
var middle = leaf.items.slice()[i];
|
||||
const rightStart: Chunk = try .init(middle.slice()[splitText..]);
|
||||
|
||||
// Build right tree
|
||||
var right = try self.gpa.create(Tree);
|
||||
errdefer right.deinit(self.gpa);
|
||||
right.* = .{ .leaf = .{
|
||||
.summary = rightStart.summary(void{}),
|
||||
.items = .init,
|
||||
.itemSummaries = .init,
|
||||
} };
|
||||
try right.leaf.items.push(rightStart);
|
||||
try right.leaf.itemSummaries.push(rightStart.summary(void{}));
|
||||
for (leaf.items.slice()[i + 1 ..]) |item| {
|
||||
try right.leaf.items.push(item);
|
||||
const sum = item.summary(void{});
|
||||
try right.leaf.itemSummaries.push(sum);
|
||||
right.leaf.summary = right.leaf.summary.add(&sum, void{});
|
||||
}
|
||||
|
||||
// Slice left tree
|
||||
if (splitText == 0) {
|
||||
leaf.items.len = i;
|
||||
leaf.itemSummaries.len = i;
|
||||
} else {
|
||||
leaf.items.sliceMut()[i] = try .init(middle.text.slice()[0..splitText]);
|
||||
leaf.itemSummaries.sliceMut()[i] = leaf.items.slice()[i].summary(void{});
|
||||
leaf.items.len = i + 1;
|
||||
leaf.itemSummaries.len = i + 1;
|
||||
}
|
||||
leaf.summary = Chunk.Summary.zero(void{});
|
||||
for (leaf.items.slice()) |item| {
|
||||
leaf.summary = leaf.summary.add(&item.summary(void{}), void{});
|
||||
}
|
||||
self.tree.checkInvariants(void{});
|
||||
right.checkInvariants(void{});
|
||||
return right;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Joins another rope to the end of this rope.
|
||||
pub fn join(self: *Rope, other: *Rope) Error!void {
|
||||
if (other.len() == 0) {
|
||||
other.deinit();
|
||||
return;
|
||||
}
|
||||
if (self.len() == 0) {
|
||||
std.mem.swap(Tree, self.tree, other.tree);
|
||||
other.deinit();
|
||||
return;
|
||||
}
|
||||
try self.joinTree(other);
|
||||
self.tree.checkInvariants(void{});
|
||||
}
|
||||
fn joinTree(self: *Rope, other: *Rope) Error!void {
|
||||
const leftHeight = self.tree.height();
|
||||
const rightHeight = other.tree.height();
|
||||
if (leftHeight == rightHeight) {
|
||||
const parent = try self.gpa.create(Tree);
|
||||
errdefer parent.deinit(self.gpa);
|
||||
parent.* = .{ .internal = .{
|
||||
.height = leftHeight + 1,
|
||||
.summary = self.tree.summary().add(&other.tree.summary(), void{}),
|
||||
.childSummaries = .init,
|
||||
.childTrees = .init,
|
||||
} };
|
||||
|
||||
try parent.internal.childTrees.push(self.tree);
|
||||
try parent.internal.childTrees.push(other.tree);
|
||||
try parent.internal.childSummaries.push(self.summary());
|
||||
try parent.internal.childSummaries.push(other.summary());
|
||||
self.tree = parent;
|
||||
} else if (leftHeight > rightHeight) {
|
||||
try self.joinTreeEdge(false, other);
|
||||
} else {
|
||||
std.mem.swap(Tree, self.tree, other.tree);
|
||||
try self.joinTreeEdge(true, other);
|
||||
}
|
||||
other.tree = undefined;
|
||||
}
|
||||
fn joinTreeEdge(self: *Rope, comptime left: bool, other: *Rope) Error!void {
|
||||
switch (self.tree.*) {
|
||||
.internal => |*internal| {
|
||||
const i = if (left) 0 else internal.childTrees.len - 1;
|
||||
const child = internal.childTrees.slice()[i];
|
||||
|
||||
const childHeight = child.height();
|
||||
const otherHeight = other.tree.height();
|
||||
if (childHeight > otherHeight) {
|
||||
var childRope: Rope = .{ .tree = child, .gpa = self.gpa };
|
||||
try childRope.joinTreeEdge(left, other);
|
||||
|
||||
internal.childSummaries.sliceMut()[i] = child.summary();
|
||||
internal.summary = Chunk.Summary.zero(void{});
|
||||
for (internal.childSummaries.slice()) |sum| {
|
||||
internal.summary = internal.summary.add(&sum, void{});
|
||||
}
|
||||
} else if (childHeight == otherHeight) {
|
||||
if (left) {
|
||||
try internal.childTrees.insert(0, other.tree);
|
||||
try internal.childSummaries.insert(0, other.summary());
|
||||
} else {
|
||||
try internal.childTrees.push(other.tree);
|
||||
try internal.childSummaries.push(other.summary());
|
||||
}
|
||||
internal.summary = internal.summary.add(&other.summary(), void{});
|
||||
} else {
|
||||
const parent = try self.gpa.create(Tree);
|
||||
errdefer parent.deinit(self.gpa);
|
||||
parent.* = .{ .internal = .{
|
||||
.height = self.tree.height() + 1,
|
||||
.summary = other.summary().add(&child.summary(), void{}),
|
||||
.childSummaries = .init,
|
||||
.childTrees = .init,
|
||||
} };
|
||||
|
||||
const first = if (left) other.tree else child;
|
||||
const second = if (left) child else other.tree;
|
||||
try parent.internal.childTrees.push(first);
|
||||
try parent.internal.childTrees.push(second);
|
||||
try parent.internal.childSummaries.push(first.summary());
|
||||
try parent.internal.childSummaries.push(second.summary());
|
||||
|
||||
internal.childTrees.sliceMut()[i] = parent;
|
||||
internal.childSummaries.sliceMut()[i] = parent.summary();
|
||||
internal.summary = Chunk.Summary.zero(void{});
|
||||
for (internal.childSummaries.slice()) |sum| {
|
||||
internal.summary = internal.summary.add(&sum, void{});
|
||||
}
|
||||
}
|
||||
},
|
||||
.leaf => {
|
||||
const parent = try self.gpa.create(Tree);
|
||||
errdefer parent.deinit(self.gpa);
|
||||
|
||||
parent.* = .{ .internal = .{
|
||||
.height = @max(self.tree.height(), other.tree.height()) + 1,
|
||||
.summary = self.tree.summary().add(&other.tree.summary(), void{}),
|
||||
.childSummaries = .init,
|
||||
.childTrees = .init,
|
||||
} };
|
||||
|
||||
const first = if (left) other.tree else self.tree;
|
||||
const second = if (left) self.tree else other.tree;
|
||||
try parent.internal.childTrees.push(first);
|
||||
try parent.internal.childTrees.push(second);
|
||||
try parent.internal.childSummaries.push(first.summary());
|
||||
try parent.internal.childSummaries.push(second.summary());
|
||||
|
||||
self.tree = parent;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the summary of the rope's contents.
|
||||
pub fn summary(self: *const Rope) Chunk.Summary {
|
||||
return self.tree.summary();
|
||||
}
|
||||
|
||||
/// Returns the length of the rope's contents in bytes.
|
||||
pub fn len(self: *const Rope) usize {
|
||||
return self.summary().len;
|
||||
}
|
||||
|
||||
/// Returns the number of newline characters in the rope.
|
||||
pub fn lines(self: *const Rope) usize {
|
||||
return self.summary().lines.row;
|
||||
}
|
||||
|
||||
pub fn pointToOffset(self: *const Rope, point: Point) usize {
|
||||
const selfSummary = self.summary();
|
||||
if (point.cmp(&selfSummary.lines).gte()) return selfSummary.len;
|
||||
|
||||
const result = self.tree.find(sumTree.Dimensions(Point, Point.USize, null), Point, void{}, &point, .left);
|
||||
const start = result.start;
|
||||
const item = result.item;
|
||||
const overshoot = point.sub(&start.d1);
|
||||
return start.d2.inner + if (item) |chunk| chunk.asSlice().pointToOffset(overshoot) else 0;
|
||||
}
|
||||
|
||||
pub fn offsetToPoint(self: *const Rope, offset: usize) Point {
|
||||
const sum = self.summary();
|
||||
if (offset > sum.len) {
|
||||
return sum.lines;
|
||||
}
|
||||
const target: Point.USize = .{ .inner = offset };
|
||||
const result = self.tree.find(sumTree.Dimensions(Point.USize, Point, null), Point.USize, void{}, &target, .left);
|
||||
var start = result.start;
|
||||
const item = result.item;
|
||||
const overshoot = offset - start.d1.inner;
|
||||
return start.d2.add(if (item) |chunk| &chunk.asSlice().offsetToPoint(overshoot) else &Point.zero(void{}));
|
||||
}
|
||||
|
||||
/// Writes the contents of the rope to the writer, without flushing it.
|
||||
pub fn format(self: *const Rope, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
const visitor: Tree.ThisVisitor(*std.Io.Writer, std.Io.Writer.Error) = .{
|
||||
.visitInternal = null,
|
||||
.visitLeaf = formatVisitor,
|
||||
};
|
||||
try visitor.visit(self.tree, writer);
|
||||
}
|
||||
fn formatVisitor(leaf: *const Tree.Leaf, context: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
for (leaf.items.slice()) |chunk| {
|
||||
const s = chunk.slice();
|
||||
_ = try context.write(s);
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates and returns a string with the contents of the rope written to the string.
|
||||
///
|
||||
/// Deallocates the string upon error.
|
||||
pub fn toString(
|
||||
self: *const Rope,
|
||||
gpa: std.mem.Allocator,
|
||||
) (std.mem.Allocator.Error || std.Io.Writer.Error)![]u8 {
|
||||
var buffer = try gpa.alloc(u8, self.len());
|
||||
errdefer gpa.free(buffer);
|
||||
var stream = std.Io.Writer.fixed(&buffer);
|
||||
self.format(&stream);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
pub fn cursor(self: *const Rope, start: usize) Cursor {
|
||||
return .{ .rope = self.tree, .start = start };
|
||||
}
|
||||
|
||||
pub const Cursor = struct {
|
||||
rope: *const Tree,
|
||||
start: usize = 0,
|
||||
|
||||
pub fn slice(self: *Cursor, end: usize) Slice {
|
||||
std.debug.assert(end <= self.rope.summary().len);
|
||||
var current = self.rope;
|
||||
var currentStart: usize = 0;
|
||||
var currentEnd = self.rope.summary().len;
|
||||
|
||||
current: while (true) {
|
||||
// Find the shallowest node containing `offset..endOffset`.
|
||||
switch (current.*) {
|
||||
.internal => |*internal| {
|
||||
var thisStart = currentStart;
|
||||
for (internal.childTrees.slice(), internal.childSummaries.slice()) |t, s| {
|
||||
const thisEnd = thisStart + s.len;
|
||||
if (thisStart <= self.start and thisEnd >= end) {
|
||||
currentStart = thisStart;
|
||||
currentEnd = thisEnd;
|
||||
current = t;
|
||||
continue :current;
|
||||
}
|
||||
thisStart = thisEnd;
|
||||
}
|
||||
return .{
|
||||
.tree = current,
|
||||
.trimStart = self.start - currentStart,
|
||||
.trimEnd = currentEnd - end,
|
||||
};
|
||||
},
|
||||
.leaf => {
|
||||
return .{
|
||||
.tree = current,
|
||||
.trimStart = self.start - currentStart,
|
||||
.trimEnd = currentEnd - end,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Slice = struct {
|
||||
tree: *const Tree,
|
||||
trimStart: usize,
|
||||
trimEnd: usize,
|
||||
|
||||
pub fn len(self: *const Slice) usize {
|
||||
return self.tree.summary().len -| self.trimStart -| self.trimEnd;
|
||||
}
|
||||
|
||||
pub fn visit(
|
||||
self: *const Slice,
|
||||
comptime C: type,
|
||||
comptime E: ?type,
|
||||
f: fn ([]const u8, C) if (E) |_E| _E!void else void,
|
||||
context: C,
|
||||
) if (E) |_E| _E!void else void {
|
||||
return self.visitDirection(false, C, E, f, context);
|
||||
}
|
||||
pub fn visitReverse(
|
||||
self: *const Slice,
|
||||
comptime C: type,
|
||||
comptime E: ?type,
|
||||
f: fn ([]const u8, C) if (E) |_E| _E!void else void,
|
||||
context: C,
|
||||
) if (E) |_E| _E!void else void {
|
||||
return self.visitDirection(true, C, E, f, context);
|
||||
}
|
||||
pub fn visitDirection(
|
||||
self: *const Slice,
|
||||
comptime reverse: bool,
|
||||
comptime C: type,
|
||||
comptime E: ?type,
|
||||
f: fn ([]const u8, C) if (E) |_E| _E!void else void,
|
||||
context: C,
|
||||
) if (E) |_E| _E!void else void {
|
||||
const Visit = struct {
|
||||
const Context = struct {
|
||||
context: C,
|
||||
trim: usize,
|
||||
remainder: usize,
|
||||
};
|
||||
|
||||
pub fn visitLeaf(leaf: *const Tree.Leaf, c: *Context) if (E) |_E| _E!void else void {
|
||||
// Start by finding chunk bounds
|
||||
var chunks = leaf.items.slice();
|
||||
var firstChunk: usize = 0;
|
||||
var lastChunk = chunks.len - 1;
|
||||
var trimStart: usize = 0;
|
||||
var trimEnd: usize = 0;
|
||||
|
||||
// Seek to first bound
|
||||
const firstChunkTerminal = if (reverse) &lastChunk else &firstChunk;
|
||||
const firstTrimTerminal = if (reverse) &trimEnd else &trimStart;
|
||||
for (firstChunk..lastChunk + 1) |i| {
|
||||
const index = if (reverse) chunks.len - i - 1 else i;
|
||||
const chunk = chunks[index].slice();
|
||||
if (c.trim < chunk.len) {
|
||||
firstChunkTerminal.* = index;
|
||||
firstTrimTerminal.* = c.trim;
|
||||
c.trim = 0;
|
||||
break;
|
||||
}
|
||||
c.trim -= chunk.len;
|
||||
}
|
||||
// Seek to last bound
|
||||
const lastChunkTerminal = if (reverse) &firstChunk else &lastChunk;
|
||||
const lastTrimTerminal = if (reverse) &trimStart else &trimEnd;
|
||||
for (firstChunk..lastChunk + 1) |i| {
|
||||
const index = if (reverse) lastChunk - i - 1 else i;
|
||||
const chunk = chunks[index].slice();
|
||||
var length = chunk.len;
|
||||
if (i == firstChunk) {
|
||||
length -= firstTrimTerminal.*;
|
||||
}
|
||||
if (c.remainder < length) {
|
||||
lastChunkTerminal.* = index;
|
||||
lastTrimTerminal.* = length - c.remainder;
|
||||
c.remainder = 0;
|
||||
break;
|
||||
}
|
||||
c.remainder -= length;
|
||||
}
|
||||
|
||||
// Then iterate through chunk bounds in given order
|
||||
chunks = chunks[firstChunk .. lastChunk + 1];
|
||||
for (0..chunks.len) |i| {
|
||||
const index = if (reverse) chunks.len - i - 1 else i;
|
||||
var chunk = chunks[index].slice();
|
||||
if (index == 0) {
|
||||
chunk = chunk[trimStart..];
|
||||
}
|
||||
if (index == chunks.len - 1) {
|
||||
chunk = chunk[0 .. chunk.len - trimEnd];
|
||||
}
|
||||
if (E) |_| {
|
||||
try f(chunk, c.context);
|
||||
} else {
|
||||
f(chunk, c.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const visitor: Tree.ThisVisitor(*Visit.Context, E) = .{
|
||||
.visitInternal = null,
|
||||
.visitLeaf = Visit.visitLeaf,
|
||||
};
|
||||
var c: Visit.Context = .{
|
||||
.context = context,
|
||||
.trim = if (reverse) self.trimEnd else self.trimStart,
|
||||
.remainder = self.len(),
|
||||
};
|
||||
return visitor.visitDirection(reverse, self.tree, &c);
|
||||
}
|
||||
|
||||
/// Writes the contents of the rope to the writer, without flushing it.
|
||||
pub fn format(self: *const Slice, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
try self.visit(*std.Io.Writer, std.Io.Writer.Error, sliceFormatVisitor, writer);
|
||||
}
|
||||
fn sliceFormatVisitor(chunk: []const u8, context: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
_ = try context.write(chunk);
|
||||
}
|
||||
};
|
||||
|
||||
test read {
|
||||
// Arrange
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
// output
|
||||
var buffer: [44]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
var rope: Rope = try .init(alloc);
|
||||
|
||||
// Act
|
||||
try rope.read(&reader);
|
||||
|
||||
// Assert
|
||||
try writer.print("{f}", .{rope});
|
||||
try std.testing.expect(source.len > Chunk.MAX_BASE);
|
||||
try std.testing.expectEqualStrings(source, &buffer);
|
||||
rope.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
|
||||
test slice {
|
||||
// Arrange
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
// output
|
||||
var buffer: [15]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
|
||||
// Act
|
||||
const ropeSlice = rope.slice(10, 25);
|
||||
|
||||
// Assert
|
||||
try writer.print("{f}", .{ropeSlice});
|
||||
try std.testing.expectEqualStrings("brown fox\njumps", &buffer);
|
||||
rope.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
|
||||
test sliceRows {
|
||||
// Arrange
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
// output
|
||||
var buffer: [10]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
|
||||
// Act
|
||||
const ropeSlice = rope.sliceRows(1, 2);
|
||||
|
||||
// Assert
|
||||
try writer.print("{f}", .{ropeSlice});
|
||||
try std.testing.expectEqualStrings("jumps\nover", &buffer);
|
||||
rope.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
|
||||
test sliceLine {
|
||||
// Arrange
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
// output
|
||||
var buffer: [5]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
|
||||
// Act
|
||||
const ropeSlice = rope.sliceLine(1);
|
||||
|
||||
// Assert
|
||||
try writer.print("{f}", .{ropeSlice});
|
||||
try std.testing.expectEqualStrings("jumps", &buffer);
|
||||
rope.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
|
||||
test pointToOffset {
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
// 0123456789012345678 901234 5678
|
||||
// 0:0 1:0 2:0
|
||||
// 2:2
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
// -----------------------------------------^ row 2 (26 chars)
|
||||
// -------------------------------------------^ row 2, column 2 (28 chars)
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
const offset = rope.pointToOffset(.{ .column = 2, .row = 2 });
|
||||
|
||||
try std.testing.expectEqual(28, offset);
|
||||
}
|
||||
|
||||
test offsetToPoint {
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
// 0123456789012345678 901234 5678
|
||||
// 0:0 1:0 2:0
|
||||
// 2:2
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
// -----------------------------------------^ row 2 (26 chars)
|
||||
// -------------------------------------------^ row 2, column 2 (28 chars)
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
|
||||
const point = rope.offsetToPoint(28);
|
||||
|
||||
try std.testing.expectEqual(2, point.column);
|
||||
try std.testing.expectEqual(2, point.row);
|
||||
}
|
||||
|
||||
test split {
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
// 0123456789012345678 901234 5
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
// output
|
||||
var bufferLeft: [26]u8 = undefined;
|
||||
var writerLeft = std.Io.Writer.fixed(&bufferLeft);
|
||||
var bufferRight: [18]u8 = undefined;
|
||||
var writerRight = std.Io.Writer.fixed(&bufferRight);
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
|
||||
// Act
|
||||
var right = (try rope.split(26)).?;
|
||||
|
||||
// Assert
|
||||
try writerLeft.print("{f}", .{rope});
|
||||
try std.testing.expectEqualStrings("The quick brown fox\njumps\n", &bufferLeft);
|
||||
try writerRight.print("{f}", .{right});
|
||||
try std.testing.expectEqualStrings("over\nthe\nlazy\ndog.", &bufferRight);
|
||||
rope.deinit();
|
||||
right.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
|
||||
test join {
|
||||
// Arrange
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
const leftSource = "The quick brown fox\njumps\n";
|
||||
const rightSource = "over\nthe\nlazy\ndog.";
|
||||
var reader: std.Io.Reader = .fixed(leftSource);
|
||||
var leftRope: Rope = try .init(alloc);
|
||||
try leftRope.read(&reader);
|
||||
reader = .fixed(rightSource);
|
||||
var rightRope: Rope = try .init(alloc);
|
||||
try rightRope.read(&reader);
|
||||
// output
|
||||
var buffer: [44]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
|
||||
// Act
|
||||
try leftRope.join(&rightRope);
|
||||
|
||||
// Assert
|
||||
try writer.print("{f}", .{leftRope});
|
||||
try std.testing.expectEqualStrings("The quick brown fox\njumps\nover\nthe\nlazy\ndog.", &buffer);
|
||||
leftRope.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
|
||||
test insert {
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
// 0123456789012345678 901234 5
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
var buffer: [49]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
|
||||
try rope.insert(10, "cool ");
|
||||
|
||||
try writer.print("{f}", .{rope});
|
||||
try std.testing.expectEqualStrings("The quick cool brown fox\njumps\nover\nthe\nlazy\ndog.", &buffer);
|
||||
rope.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
|
||||
test remove {
|
||||
// Arrange
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
// 01234567890123456789
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
// output
|
||||
var buffer: [43]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
|
||||
// Act
|
||||
try rope.remove(19);
|
||||
|
||||
// Assert
|
||||
try writer.print("{f}", .{rope});
|
||||
try std.testing.expectEqualStrings("The quick brown foxjumps\nover\nthe\nlazy\ndog.", &buffer);
|
||||
rope.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
|
||||
test "post edit" {
|
||||
// Arrange
|
||||
var gpa = std.testing.allocator_instance;
|
||||
const alloc = gpa.allocator();
|
||||
// input
|
||||
// 01234567890123456789
|
||||
const source = "The quick brown fox\njumps\nover\nthe\nlazy\ndog.";
|
||||
var reader = std.Io.Reader.fixed(source);
|
||||
// output
|
||||
var buffer: [42]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
var rope: Rope = try .init(alloc);
|
||||
try rope.read(&reader);
|
||||
|
||||
// Act
|
||||
try rope.remove(Chunk.MAX_BASE);
|
||||
rope.tree.checkInvariants(void{});
|
||||
try rope.remove(Chunk.MAX_BASE);
|
||||
rope.tree.checkInvariants(void{});
|
||||
const ropeSlice = rope.sliceRows(0, 6);
|
||||
|
||||
// Assert
|
||||
try std.testing.expectEqual(5, rope.sliceLine(1).len());
|
||||
try writer.print("{f}", .{ropeSlice});
|
||||
try std.testing.expectEqualStrings("The quick brown x\njumps\nover\nthe\nlazy\ndog.", &buffer);
|
||||
rope.deinit();
|
||||
try std.testing.expectEqual(std.heap.Check.ok, gpa.deinit());
|
||||
}
|
||||
fn debugSummary(tree: *const Tree, indent: usize) void {
|
||||
if (indent == 0) {
|
||||
std.debug.print("summary:\n", .{});
|
||||
}
|
||||
for (0..indent) |_| {
|
||||
std.debug.print(" ", .{});
|
||||
}
|
||||
const sum = tree.summary();
|
||||
std.debug.print("len: {d}, lines: {any}, type: ", .{ sum.len, sum.lines });
|
||||
switch (tree.*) {
|
||||
.internal => |*internal| {
|
||||
std.debug.print("internal\n", .{});
|
||||
for (internal.childTrees.slice()) |t| {
|
||||
debugSummary(t, indent + 1);
|
||||
}
|
||||
},
|
||||
.leaf => |*leaf| {
|
||||
std.debug.print("leaf\n", .{});
|
||||
for (0..indent) |_| {
|
||||
std.debug.print(" ", .{});
|
||||
}
|
||||
var n: usize = 0;
|
||||
for (leaf.items.slice()) |item| {
|
||||
n += item.text.len;
|
||||
std.debug.print("'", .{});
|
||||
std.debug.print("{s}", .{item.text.slice()});
|
||||
std.debug.print("' ({d} bytes, {b:0>16} newlines bitmap slice) ", .{ item.text.len, item.asSlice().sliceBitmap(item.newlines) });
|
||||
}
|
||||
std.debug.print(" ({d} bytes total)\n", .{n});
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
const std = @import("std");
|
||||
|
||||
const iterator = @import("../iter.zig");
|
||||
|
||||
pub const Error = error{
|
||||
OutOfRange,
|
||||
};
|
||||
|
||||
/// An array vector is a fixed-capacity array.
|
||||
pub fn ArrayVec(comptime T: type, comptime capacity: usize) type {
|
||||
return struct {
|
||||
const Private = struct { buffer: [capacity]T = undefined };
|
||||
|
||||
len: usize = 0,
|
||||
private: Private = .{},
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const init: Self = .{};
|
||||
|
||||
pub inline fn isFull(self: *const Self) bool {
|
||||
return self.len >= capacity;
|
||||
}
|
||||
|
||||
/// Pushes the item to the end of the array and increments the length.
|
||||
pub fn push(self: *Self, item: T) Error!void {
|
||||
if (self.isFull()) return Error.OutOfRange;
|
||||
self.private.buffer[self.len] = item;
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
/// Inserts the item at the given index, moving all the following
|
||||
/// along the vec to make space.
|
||||
pub fn insert(self: *Self, index: usize, item: T) Error!void {
|
||||
if (self.isFull()) return Error.OutOfRange;
|
||||
@memcpy(
|
||||
self.private.buffer[index + 1 .. self.len + 1],
|
||||
self.private.buffer[index..self.len],
|
||||
);
|
||||
self.private.buffer[index] = item;
|
||||
}
|
||||
|
||||
/// Pushes the items to the end of the array and updates the length.
|
||||
pub fn extend(self: *Self, items: []const T) Error!void {
|
||||
if (self.len + items.len > capacity) return Error.OutOfRange;
|
||||
@memcpy(self.private.buffer[self.len .. self.len + items.len], items);
|
||||
self.len += items.len;
|
||||
}
|
||||
|
||||
pub fn from(items: []const T) Error!Self {
|
||||
var result: Self = .init;
|
||||
try result.extend(items);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns a copy of the last element in the array and decrements the length.
|
||||
pub fn pop(self: *Self) ?T {
|
||||
if (self.len == 0) return null;
|
||||
self.len -= 1;
|
||||
return self.private.buffer[self.len];
|
||||
}
|
||||
|
||||
pub fn iter(self: *const Self) iterator.Iter(iterator.SliceIter(T)) {
|
||||
return .{ .impl = .{ .buffer = self.slice() } };
|
||||
}
|
||||
|
||||
/// Returns a copy of the element at the given index.
|
||||
pub fn get(self: *const Self, index: usize) ?T {
|
||||
if (self.isFull()) return null;
|
||||
return self.private.buffer[index];
|
||||
}
|
||||
|
||||
/// Returns a slice of the underlying array.
|
||||
pub fn slice(self: *const Self) []const T {
|
||||
return self.private.buffer[0..self.len];
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the underlying array.
|
||||
pub fn sliceMut(self: *Self) []T {
|
||||
return self.private.buffer[0..self.len];
|
||||
}
|
||||
|
||||
/// Returns a copy of the last element of the array.
|
||||
pub fn last(self: *const Self) ?T {
|
||||
if (self.len == 0) return null;
|
||||
return self.private.buffer[self.len - 1];
|
||||
}
|
||||
|
||||
/// Returns a mutable reference of the last element of the array.
|
||||
pub fn lastMut(self: *Self) ?*T {
|
||||
if (self.len == 0) return null;
|
||||
return &self.private.buffer[self.len - 1];
|
||||
}
|
||||
|
||||
/// Removes all items from the array.
|
||||
pub fn clear(self: *Self) void {
|
||||
self.len = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
//! A chunk is a small set of bytes of a text string.
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
|
||||
const iters = @import("../iter.zig");
|
||||
|
||||
const arrayVec = @import("array-vec.zig");
|
||||
const Point = @import("point.zig");
|
||||
|
||||
const ArrayVec = arrayVec.ArrayVec;
|
||||
|
||||
const Chunk = @This();
|
||||
|
||||
const Bitmap = if (builtin.is_test) u16 else u128;
|
||||
const Bitsize = std.math.Log2Int(Bitmap);
|
||||
// const Bitmap = u128;
|
||||
|
||||
pub const MAX_BASE = @bitSizeOf(Bitmap);
|
||||
pub const MIN_BASE = MAX_BASE / 2;
|
||||
|
||||
/// Each bit indicates the start of a UTF-8 character.
|
||||
chars: Bitmap,
|
||||
/// The sum of the bits if the number of Utf16 code units it would take to represent the text.
|
||||
charsUtf16: Bitmap,
|
||||
/// Each bit indicates a `\n` character.
|
||||
newlines: Bitmap,
|
||||
/// Each bit indicates a `\t` character.
|
||||
tabs: Bitmap,
|
||||
/// The string of bytes.
|
||||
text: ArrayVec(u8, MAX_BASE),
|
||||
|
||||
/// A summary of a string of text.
|
||||
pub const Summary = struct {
|
||||
/// The length in bytes.
|
||||
len: usize,
|
||||
/// A point one character to the right of the last character's position.
|
||||
///
|
||||
/// So the `row` field is the number of lines and `column` is the length of the last line.
|
||||
lines: Point,
|
||||
|
||||
pub const Context = void;
|
||||
|
||||
/// Creates a summary matching that of `""`.
|
||||
pub fn zero(cx: Context) Summary {
|
||||
return .{ .len = 0, .lines = .zero(cx) };
|
||||
}
|
||||
|
||||
/// Returns the addition of each field within the summaries.
|
||||
pub fn add(self: *const Summary, other: *const Summary, _: Context) Summary {
|
||||
return .{
|
||||
.len = self.len + other.len,
|
||||
.lines = self.lines.add(&other.lines),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the subtraction of the each field within the summaries.
|
||||
pub fn sub(self: *const Summary, other: *const Summary, _: Context) Summary {
|
||||
return .{
|
||||
.len = self.len - other.len,
|
||||
.lines = self.lines.sub(&other.lines),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn eq(self: *const Summary, other: *const Summary, _: Context) bool {
|
||||
return self.len == other.len and self.lines.cmp(&other.lines) == .equal;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(text: []const u8) arrayVec.Error!Chunk {
|
||||
const CHUNK_SIZE = 8;
|
||||
|
||||
var charsBytes = std.mem.zeroes([MAX_BASE / CHUNK_SIZE]u8);
|
||||
var newlinesBytes = std.mem.zeroes([MAX_BASE / CHUNK_SIZE]u8);
|
||||
var tabsBytes = std.mem.zeroes([MAX_BASE / CHUNK_SIZE]u8);
|
||||
var charsUtf16Bytes = std.mem.zeroes([MAX_BASE / CHUNK_SIZE]u8);
|
||||
|
||||
var chunkIx: u8 = 0;
|
||||
|
||||
var bytes = text;
|
||||
while (bytes.len > 0) {
|
||||
const chunk = bytes[0..@min(bytes.len, CHUNK_SIZE)];
|
||||
bytes = bytes[@min(bytes.len, CHUNK_SIZE)..];
|
||||
|
||||
var chars: u8 = 0;
|
||||
var newlines: u8 = 0;
|
||||
var tabs: u8 = 0;
|
||||
var charsUtf16: u8 = 0;
|
||||
for (0.., chunk) |i, b| {
|
||||
const ix: u3 = @intCast(i);
|
||||
const char: u8 = @intCast(@intFromBool(isUtf8CharBoundary(b)));
|
||||
chars |= char << ix;
|
||||
|
||||
const newline: u8 = @intCast(@intFromBool(b == '\n'));
|
||||
newlines |= newline << ix;
|
||||
|
||||
const tab: u8 = @intCast(@intFromBool(b == '\t'));
|
||||
tabs |= tab << ix;
|
||||
|
||||
const charUtf16: u8 = @intCast(@intFromBool(b >= 240));
|
||||
charsUtf16 |= charUtf16 << ix;
|
||||
}
|
||||
|
||||
charsBytes[chunkIx] = chars;
|
||||
newlinesBytes[chunkIx] = newlines;
|
||||
tabsBytes[chunkIx] = tabs;
|
||||
charsUtf16Bytes[chunkIx] = charsUtf16;
|
||||
|
||||
chunkIx += 1;
|
||||
}
|
||||
|
||||
const chars = std.mem.readInt(Bitmap, &charsBytes, .little);
|
||||
|
||||
return .{
|
||||
.text = try .from(text),
|
||||
.chars = chars,
|
||||
.charsUtf16 = (std.mem.readInt(Bitmap, &charsUtf16Bytes, .little) >> 1) | chars,
|
||||
.newlines = (std.mem.readInt(Bitmap, &newlinesBytes, .little)),
|
||||
.tabs = (std.mem.readInt(Bitmap, &tabsBytes, .little)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: *const Chunk, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
try std.zig.stringEscape(self.text.slice(), writer);
|
||||
}
|
||||
|
||||
/// Returns the summary of the chunk's contents.
|
||||
pub fn summary(self: *const Chunk, _: Summary.Context) Summary {
|
||||
return self.asSlice().textSummary();
|
||||
}
|
||||
|
||||
/// Returns the contents of the chunk as bytes.
|
||||
pub fn slice(self: *const Chunk) []const u8 {
|
||||
return self.text.slice();
|
||||
}
|
||||
|
||||
pub fn iter(self: *const Chunk) iters.Iter(iters.SliceIter(u8)) {
|
||||
return self.text.iter();
|
||||
}
|
||||
|
||||
pub const Slice = struct {
|
||||
text: []const u8,
|
||||
inner: *const Chunk,
|
||||
|
||||
/// Returns a bitmap with a popcount and leading zeroes outside of the text range
|
||||
/// omitted. Ensures correct `@popcout` and `@clz` after the chunk may have been shortened.
|
||||
/// Invalidates `@ctz`.
|
||||
pub fn sliceBitmap(self: *const Slice, bitmap: Bitmap) Bitmap {
|
||||
return bitmap << @as(std.math.Log2Int(Bitmap), @intCast(@max(MAX_BASE, self.text.len) - self.text.len));
|
||||
}
|
||||
|
||||
pub fn textSummary(self: *const Slice) Summary {
|
||||
return .{
|
||||
.len = self.len(),
|
||||
.lines = self.lines(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the point representing the last row and column of the chunk.
|
||||
pub fn lines(self: *const Slice) Point {
|
||||
const newlines = self.sliceBitmap(self.inner.newlines);
|
||||
// The row is the number of positive bits.
|
||||
const row = @popCount(newlines);
|
||||
// The column is the number of zero bits from the start of the chunk (NOTE: The bitmap is little endian).
|
||||
// If `@popCount` is zero, then `newlines` is zero and `@clz` will need to be clamped to `self.text.len`.
|
||||
const column = @min(@clz(newlines), self.text.len);
|
||||
return .{ .row = row, .column = column };
|
||||
}
|
||||
|
||||
pub fn len(self: *const Slice) usize {
|
||||
return self.text.len;
|
||||
}
|
||||
|
||||
pub fn pointToOffset(self: *const Slice, point: Point) usize {
|
||||
if (point.row > self.lines().row) {
|
||||
return self.len();
|
||||
}
|
||||
|
||||
const rowOffsetRange = self.offsetRangeForRow(point.row);
|
||||
if (point.column > (rowOffsetRange.end - rowOffsetRange.start)) {
|
||||
return rowOffsetRange.end;
|
||||
} else {
|
||||
return rowOffsetRange.start + point.column;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offsetToPoint(self: *const Slice, offset: usize) Point {
|
||||
const mask = (@as(Bitmap, 1) <<| offset) -% 1;
|
||||
const newlines = self.inner.newlines;
|
||||
const row = @popCount(newlines & mask);
|
||||
const newline = @bitSizeOf(Bitmap) - @clz(newlines & mask);
|
||||
const column = offset - newline;
|
||||
return .{ .row = row, .column = column };
|
||||
}
|
||||
|
||||
pub fn offsetRangeForRow(self: *const Slice, row: usize) struct { start: usize, end: usize } {
|
||||
const newlines = self.inner.newlines;
|
||||
var rowStart: usize = 0;
|
||||
if (row > 0) {
|
||||
const newlinesWide: u128 = if (builtin.is_test) @intCast(newlines) else newlines;
|
||||
rowStart = nthSetBit(newlinesWide, row) + 1;
|
||||
}
|
||||
var rowLen: usize = 0;
|
||||
if (rowStart != MAX_BASE) {
|
||||
rowLen = @min(@ctz(newlines >> @truncate(rowStart)), self.text.len - rowStart);
|
||||
}
|
||||
return .{ .start = rowStart, .end = rowStart + rowLen };
|
||||
}
|
||||
};
|
||||
pub fn asSlice(self: *const Chunk) Slice {
|
||||
return .{
|
||||
.text = self.text.slice(),
|
||||
.inner = self,
|
||||
};
|
||||
}
|
||||
|
||||
fn nthSetBit(v: u128, n: usize) usize {
|
||||
const low: u64 = @truncate(v);
|
||||
const high: u64 = @truncate(v >> 64);
|
||||
|
||||
const lowCount = @popCount(low);
|
||||
if (n > lowCount) {
|
||||
return 64 + nthSetBitU64(high, n - lowCount);
|
||||
} else {
|
||||
return nthSetBitU64(low, n);
|
||||
}
|
||||
}
|
||||
fn nthSetBitU64(vBase: u64, nBase: u64) u64 {
|
||||
// https://zed.dev/blog/zed-decoded-rope-optimizations-part-1
|
||||
const v = @bitReverse(vBase);
|
||||
var n = nBase;
|
||||
var s: u64 = 64;
|
||||
|
||||
const U64_MAX = std.math.maxInt(u64);
|
||||
// Parallel bit count intermediates
|
||||
const a = v - ((v >> 1) & (U64_MAX / 3));
|
||||
const b = (a & (U64_MAX / 5)) + ((a >> 2) & (U64_MAX / 5));
|
||||
const c = (b + (b >> 4)) & (U64_MAX / 0x11);
|
||||
const d = (c + (c >> 8)) & (U64_MAX / 0x101);
|
||||
|
||||
// Branchless select
|
||||
var t = (d >> 32) + (d >> 48);
|
||||
s -= ((t -% n) & 256) >> 3;
|
||||
n -= t & ((t -% n) >> 8);
|
||||
|
||||
t = (d >> @intCast(s - 16)) & 0xff;
|
||||
s -= ((t -% n) & 256) >> 4;
|
||||
n -= t & ((t -% n) >> 8);
|
||||
|
||||
t = (c >> @intCast(s - 8)) & 0xf;
|
||||
s -= ((t -% n) & 256) >> 5;
|
||||
n -= t & ((t -% n) >> 8);
|
||||
|
||||
t = (b >> @intCast(s - 4)) & 0x7;
|
||||
s -= ((t -% n) & 256) >> 6;
|
||||
n -= t & ((t -% n) >> 8);
|
||||
|
||||
t = (a >> @intCast(s - 2)) & 0x3;
|
||||
s -= ((t -% n) & 256) >> 7;
|
||||
n -= t & ((t -% n) >> 8);
|
||||
|
||||
t = (v >> @intCast(s - 1)) & 0x1;
|
||||
s -= ((t -% n) & 256) >> 8;
|
||||
|
||||
return 65 - s - 1;
|
||||
}
|
||||
|
||||
fn isUtf8CharBoundary(@"u8": u8) bool {
|
||||
// This is bit magic equivalent to: b < 128 || b >= 192
|
||||
const @"i8": i8 = @intCast(@"u8");
|
||||
return @"i8" >= -0x40;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
const Ordering = @import("../ordering.zig").Ordering;
|
||||
const Chunk = @import("chunk.zig");
|
||||
const sumTree = @import("sum-tree.zig");
|
||||
|
||||
const Point = @This();
|
||||
|
||||
row: usize,
|
||||
column: usize,
|
||||
|
||||
pub const Summary = Chunk.Summary;
|
||||
pub const Context = Summary.Context;
|
||||
|
||||
pub fn init(row: usize, column: usize) Point {
|
||||
return .{ .row = row, .column = column };
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Point) Point {
|
||||
return .{ .row = self.row, .column = self.column };
|
||||
}
|
||||
|
||||
pub fn add(self: *const Point, other: *const Point) Point {
|
||||
if (other.row == 0) {
|
||||
return .{ .row = self.row, .column = self.column + other.column };
|
||||
} else {
|
||||
return .{ .row = self.row + other.row, .column = other.column };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub(self: *const Point, other: *const Point) Point {
|
||||
if (self.row == other.row) {
|
||||
return .{ .row = 0, .column = self.column - other.column };
|
||||
} else {
|
||||
return .{ .row = self.row - other.row, .column = self.column };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmp(self: *const Point, other: *const Point) Ordering {
|
||||
return switch (Ordering.cmp(self.row, other.row)) {
|
||||
.equal => Ordering.cmp(self.column, other.column),
|
||||
else => |ordering| ordering,
|
||||
};
|
||||
}
|
||||
|
||||
pub const USize = struct {
|
||||
inner: usize = 0,
|
||||
|
||||
pub const Summary = Chunk.Summary;
|
||||
pub const Context = void;
|
||||
|
||||
pub fn zero(_: USize.Context) USize {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn clone(self: *const USize) USize {
|
||||
return .{ .inner = self.inner };
|
||||
}
|
||||
|
||||
pub fn addSummary(self: *USize, sum: *const USize.Summary, _: USize.Context) void {
|
||||
self.inner += sum.len;
|
||||
}
|
||||
|
||||
pub fn cmp(self: *const USize, other: *const USize) Ordering {
|
||||
return Ordering.cmp(self.inner, other.inner);
|
||||
}
|
||||
|
||||
pub fn cmpSeekTarget(self: *const USize, cursorLocation: anytype, _: USize.Context) Ordering {
|
||||
return self.cmp(&cursorLocation.d1);
|
||||
}
|
||||
};
|
||||
pub fn cmpSeekTarget(
|
||||
self: *const Point,
|
||||
cursorLocation: *const sumTree.Dimensions(Point, USize, null),
|
||||
_: Summary.Context,
|
||||
) Ordering {
|
||||
return self.cmp(&cursorLocation.d1);
|
||||
}
|
||||
|
||||
pub fn zero(_: Summary.Context) Point {
|
||||
return .{ .row = 0, .column = 0 };
|
||||
}
|
||||
|
||||
pub fn addSummary(self: *Point, summary: *const Summary, _: Summary.Context) void {
|
||||
self.* = self.add(&summary.lines);
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const iters = @import("../iter.zig");
|
||||
const Ordering = @import("../ordering.zig").Ordering;
|
||||
const arrayVec = @import("array-vec.zig");
|
||||
|
||||
const ArrayVec = arrayVec.ArrayVec;
|
||||
|
||||
pub const TREE_BASE = 6;
|
||||
pub const TREE_MAX = TREE_BASE * 2;
|
||||
|
||||
pub const Error = error{
|
||||
EmptyInternal,
|
||||
};
|
||||
|
||||
pub const Bias = enum {
|
||||
left,
|
||||
right,
|
||||
|
||||
pub const default: Bias = .left;
|
||||
};
|
||||
|
||||
/// A visitor is a type which can travel across the tree in a recursive (i.e. depth-first) fashion.
|
||||
///
|
||||
/// An error can provided as a type of "break" or as a legitimate error to prematurely end the
|
||||
/// visitor.
|
||||
pub fn Visitor(comptime T: type, comptime C: type, comptime E: ?type) type {
|
||||
const R = if (E) |_E| _E!void else void;
|
||||
return struct {
|
||||
/// A function that will be called with each internal node of the sum-tree.
|
||||
visitInternal: ?*const fn (*const SumTree(T).Internal, C) R = null,
|
||||
/// A function that will be called with each leaf node of the sum-tree.
|
||||
visitLeaf: ?*const fn (*const SumTree(T).Leaf, C) R = null,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
/// Initiates the visitor against the node and all it's descendants.
|
||||
pub fn visit(self: *const Self, node: *const SumTree(T), context: C) R {
|
||||
return self.visitDirection(false, node, context);
|
||||
}
|
||||
pub fn visitReverse(self: *const Self, node: *const SumTree(T), context: C) R {
|
||||
return self.visitDirection(true, node, context);
|
||||
}
|
||||
|
||||
pub fn visitDirection(self: *const Self, comptime reverse: bool, node: *const SumTree(T), context: C) R {
|
||||
switch (node.*) {
|
||||
.internal => |*internal| {
|
||||
if (self.visitInternal) |f| {
|
||||
if (E) |_| {
|
||||
try f(internal, context);
|
||||
} else {
|
||||
f(internal, context);
|
||||
}
|
||||
}
|
||||
const children = internal.childTrees.slice();
|
||||
for (0..children.len) |i| {
|
||||
const child = if (reverse) children[children.len - i - 1] else children[i];
|
||||
if (E) |_| {
|
||||
try self.visit(child, context);
|
||||
} else {
|
||||
self.visit(child, context);
|
||||
}
|
||||
}
|
||||
},
|
||||
.leaf => |*leaf| {
|
||||
if (self.visitLeaf) |f| {
|
||||
if (E) |_| {
|
||||
try f(leaf, context);
|
||||
} else {
|
||||
f(leaf, context);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Dimensions(comptime D1: type, comptime D2: type, comptime D3: ?type) type {
|
||||
return struct {
|
||||
d1: D1,
|
||||
d2: D2,
|
||||
d3: if (D3) |d| d else void,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn zero(cx: D1.Context) Self {
|
||||
return .{ .d1 = .zero(cx), .d2 = .zero(cx), .d3 = if (D3) |_| .zero(cx) else void{} };
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Self) Self {
|
||||
return .{ .d1 = self.d1.clone(), .d2 = self.d2.clone(), .d3 = if (D3) |_| self.d3.clone() else void{} };
|
||||
}
|
||||
|
||||
pub fn withAddedSummary(self: Self, summary: *const D1.Summary, cx: D1.Summary.Context) Self {
|
||||
var result = self;
|
||||
result.addSummary(summary, cx);
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn addSummary(self: *Self, summary: *const D1.Summary, cx: D1.Summary.Context) void {
|
||||
self.d1.addSummary(summary, cx);
|
||||
self.d2.addSummary(summary, cx);
|
||||
if (D3) |_| self.d3.addSummary(summary, cx);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Cursor(comptime T: type) type {
|
||||
return struct {
|
||||
tree: *const SumTree(T),
|
||||
};
|
||||
}
|
||||
|
||||
/// A sum-tree is a tree which contains a summary of all it's child nodes, where each child
|
||||
/// node similarly contains a summary of it's children.
|
||||
///
|
||||
/// This provides a data structure that is efficient for navigation by binary searching against
|
||||
/// the summary's heuristics.
|
||||
pub fn SumTree(comptime T: type) type {
|
||||
return union(enum) {
|
||||
pub const Internal = struct {
|
||||
height: usize,
|
||||
summary: T.Summary,
|
||||
childSummaries: ArrayVec(T.Summary, TREE_MAX),
|
||||
childTrees: ArrayVec(*SumTree(T), TREE_MAX),
|
||||
};
|
||||
pub const Leaf = struct {
|
||||
summary: T.Summary,
|
||||
itemSummaries: ArrayVec(T.Summary, TREE_MAX),
|
||||
items: ArrayVec(T, TREE_MAX),
|
||||
|
||||
pub fn iter(self: *const Leaf) iters.Iter(iters.SliceIter(T)) {
|
||||
return self.items.iter();
|
||||
}
|
||||
};
|
||||
|
||||
/// A node that contains child nodes.
|
||||
internal: Internal,
|
||||
/// A node that contains data.
|
||||
leaf: Leaf,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
/// Returns a visitor type with this sum-tree's data.
|
||||
pub fn ThisVisitor(comptime C: type, comptime E: ?type) type {
|
||||
return Visitor(T, C, E);
|
||||
}
|
||||
|
||||
pub fn init(gpa: std.mem.Allocator, context: T.Summary.Context) std.mem.Allocator.Error!*Self {
|
||||
const self = try gpa.create(Self);
|
||||
self.* = .{ .leaf = .{
|
||||
.summary = .zero(context),
|
||||
.itemSummaries = .init,
|
||||
.items = .init,
|
||||
} };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, gpa: std.mem.Allocator) void {
|
||||
switch (self.*) {
|
||||
.internal => |internal| {
|
||||
for (internal.childTrees.slice()) |child| {
|
||||
child.deinit(gpa);
|
||||
}
|
||||
},
|
||||
.leaf => {},
|
||||
}
|
||||
gpa.destroy(self);
|
||||
}
|
||||
|
||||
/// Returns a summary of the node and any descendants.
|
||||
pub fn summary(self: *const Self) T.Summary {
|
||||
return switch (self.*) {
|
||||
.internal => |internal| internal.summary,
|
||||
.leaf => |leaf| leaf.summary,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the number of generations withing the current node.
|
||||
pub fn height(self: *const Self) usize {
|
||||
return switch (self.*) {
|
||||
.internal => |internal| internal.height,
|
||||
.leaf => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn cursor(self: *const Self) Cursor(T) {
|
||||
return .{ .tree = self };
|
||||
}
|
||||
|
||||
pub fn FindResult(comptime D: type) type {
|
||||
return struct { start: D, end: D, item: ?*const T };
|
||||
}
|
||||
pub fn find(
|
||||
self: *const Self,
|
||||
comptime D: type,
|
||||
comptime Target: type,
|
||||
cx: T.Summary.Context,
|
||||
target: *const Target,
|
||||
bias: Bias,
|
||||
) FindResult(D) {
|
||||
const treeEnd = D.zero(cx).withAddedSummary(&self.summary(), cx);
|
||||
const comparison = target.cmpSeekTarget(&treeEnd, cx);
|
||||
const isEnd = switch (comparison) {
|
||||
.greater => true,
|
||||
.equal => bias == .right,
|
||||
.less => false,
|
||||
};
|
||||
if (isEnd) {
|
||||
return .{ .start = treeEnd, .end = treeEnd, .item = null };
|
||||
}
|
||||
|
||||
var position = D.zero(cx);
|
||||
var current = self;
|
||||
|
||||
outer: while (true) {
|
||||
switch (current.*) {
|
||||
.internal => |*internal| {
|
||||
const childSummaries = internal.childSummaries.slice();
|
||||
const childTrees = internal.childTrees.slice();
|
||||
for (childSummaries, childTrees) |childSummary, childTree| {
|
||||
const childEnd = position.withAddedSummary(&childSummary, cx);
|
||||
const cmp = target.cmpSeekTarget(&childEnd, cx);
|
||||
const targetInChild = cmp == .less or (cmp == .equal and bias == .left);
|
||||
if (targetInChild) {
|
||||
current = childTree;
|
||||
continue :outer;
|
||||
}
|
||||
position = childEnd;
|
||||
}
|
||||
return .{ .start = position, .end = position, .item = null };
|
||||
},
|
||||
.leaf => |*leaf| {
|
||||
const itemSummaries = leaf.itemSummaries.slice();
|
||||
const items = leaf.items.slice();
|
||||
for (itemSummaries, items) |itemSummary, *item| {
|
||||
const itemEnd = position.withAddedSummary(&itemSummary, cx);
|
||||
const cmp = target.cmpSeekTarget(&itemEnd, cx);
|
||||
const itemFound = cmp == .less or (cmp == .equal and bias == .left);
|
||||
if (itemFound) {
|
||||
return .{ .start = position, .end = itemEnd, .item = item };
|
||||
}
|
||||
position = itemEnd;
|
||||
}
|
||||
return .{ .start = position, .end = position, .item = null };
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts the item to the last possible position within the tree.
|
||||
///
|
||||
/// If the item won't fit into the current tree, it will return a
|
||||
/// dangling node for the caller to use in rebalancing the tree.
|
||||
pub fn push(
|
||||
self: *Self,
|
||||
gpa: std.mem.Allocator,
|
||||
item: T,
|
||||
context: T.Summary.Context,
|
||||
) (arrayVec.Error || std.mem.Allocator.Error || Error)!?*Self {
|
||||
switch (self.*) {
|
||||
.leaf => |*leaf| {
|
||||
const itemSummary = item.summary(context);
|
||||
// If there's room, add it
|
||||
if (leaf.items.len < TREE_MAX) {
|
||||
try leaf.items.push(item);
|
||||
try leaf.itemSummaries.push(itemSummary);
|
||||
leaf.summary = leaf.summary.add(&itemSummary, context);
|
||||
return null;
|
||||
}
|
||||
// Otherwise, split it
|
||||
const newLeaf = try gpa.create(Self);
|
||||
newLeaf.* = .{ .leaf = .{
|
||||
.summary = T.Summary.zero(context),
|
||||
.items = .init,
|
||||
.itemSummaries = .init,
|
||||
} };
|
||||
|
||||
try newLeaf.leaf.items.push(item);
|
||||
try newLeaf.leaf.itemSummaries.push(itemSummary);
|
||||
newLeaf.leaf.summary = newLeaf.leaf.summary.add(&itemSummary, context);
|
||||
|
||||
// Return leaf to be handled by parent
|
||||
self.checkInvariants(context);
|
||||
return newLeaf;
|
||||
},
|
||||
.internal => |*internal| {
|
||||
const lastChild = internal.childTrees.lastMut() orelse return Error.EmptyInternal;
|
||||
|
||||
// Push and handle split
|
||||
if (try lastChild.*.push(gpa, item, context)) |newChild| {
|
||||
// If child can fit into this node, push it
|
||||
if (internal.childTrees.len < TREE_MAX) {
|
||||
try internal.childTrees.push(newChild);
|
||||
try internal.childSummaries.push(newChild.summary());
|
||||
internal.summary = internal.summary.add(&newChild.summary(), context);
|
||||
self.checkInvariants(context);
|
||||
return null;
|
||||
}
|
||||
// Otherwise, create a new node and propogate to parent
|
||||
const newInternal = try gpa.create(Self);
|
||||
newInternal.* = .{ .internal = .{
|
||||
.height = internal.height,
|
||||
.summary = newChild.summary(),
|
||||
.childSummaries = .init,
|
||||
.childTrees = .init,
|
||||
} };
|
||||
|
||||
try newInternal.internal.childTrees.push(newChild);
|
||||
try newInternal.internal.childSummaries.push(newChild.summary());
|
||||
|
||||
self.checkInvariants(context);
|
||||
return newInternal;
|
||||
} else {
|
||||
// Update our summary to match updated child
|
||||
const lastChildSummary = lastChild.*.summary();
|
||||
const internalLastChildSummary = internal.childSummaries.lastMut().?;
|
||||
const difference = lastChildSummary.sub(internalLastChildSummary, context);
|
||||
|
||||
internalLastChildSummary.* = lastChildSummary;
|
||||
internal.summary = internal.summary.add(&difference, context);
|
||||
self.checkInvariants(context);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const CheckInvariantsContext = struct {
|
||||
cx: T.Summary.Context,
|
||||
|
||||
pub fn visitInternal(internal: *const Internal, cx: *@This()) void {
|
||||
var computed = T.Summary.zero(cx.cx);
|
||||
for (internal.childSummaries.slice()) |childSummary| {
|
||||
computed = computed.add(&childSummary, cx.cx);
|
||||
}
|
||||
if (!computed.eq(&internal.summary, cx.cx)) {
|
||||
std.debug.panic(
|
||||
\\Incorrect internal summary!
|
||||
\\ Current: {any}
|
||||
\\ Computed: {any}
|
||||
\\
|
||||
, .{ internal.summary, computed });
|
||||
}
|
||||
for (internal.childTrees.slice(), internal.childSummaries.slice(), 0..) |child, childSummary, i| {
|
||||
const sum = child.summary();
|
||||
if (!sum.eq(&childSummary, cx.cx)) {
|
||||
std.debug.panic(
|
||||
\\Incorrect child summary at {d}!
|
||||
\\ Current: {any}
|
||||
\\ Computed: {any}
|
||||
\\
|
||||
, .{ i, childSummary, sum });
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn visitLeaf(leaf: *const Leaf, cx: *@This()) void {
|
||||
var computed = T.Summary.zero(cx.cx);
|
||||
for (leaf.itemSummaries.slice()) |itemSummary| {
|
||||
computed = computed.add(&itemSummary, cx.cx);
|
||||
}
|
||||
if (!computed.eq(&leaf.summary, cx.cx)) {
|
||||
std.debug.panic(
|
||||
\\Incorrect leaf summary!
|
||||
\\ Current: {any}
|
||||
\\ Computed: {any}
|
||||
\\
|
||||
, .{ leaf.summary, computed });
|
||||
}
|
||||
for (leaf.items.slice(), leaf.itemSummaries.slice(), 0..) |item, itemSummary, i| {
|
||||
const sum = item.summary(cx.cx);
|
||||
if (!sum.eq(&itemSummary, cx.cx)) {
|
||||
std.debug.panic(
|
||||
\\Incorrect item summary at {d}!
|
||||
\\ Current: {any}
|
||||
\\ Computed: {any}
|
||||
\\
|
||||
, .{ i, leaf.summary, computed });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
pub fn checkInvariants(self: *const Self, context: T.Summary.Context) void {
|
||||
comptime if (builtin.mode != .Debug) return;
|
||||
var cx: CheckInvariantsContext = .{ .cx = context };
|
||||
const visitor: ThisVisitor(*CheckInvariantsContext, null) = .{
|
||||
.visitInternal = CheckInvariantsContext.visitInternal,
|
||||
.visitLeaf = CheckInvariantsContext.visitLeaf,
|
||||
};
|
||||
|
||||
visitor.visit(self, &cx);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user