ona/source/coral/list.zig

226 lines
7.1 KiB
Zig
Executable File

const debug = @import("./debug.zig");
const io = @import("./io.zig");
const math = @import("./math.zig");
///
/// Returns a dynamically sized stack capable of holding `Element`.
///
pub fn Stack(comptime Element: type) type {
return struct {
capacity: usize,
values: []Element,
const Self = @This();
///
/// Clears all elements from `self` while preserving the current internal buffer.
///
/// To clean up memory allocations made by the stack and deinitialize it, see [deinit] instead.
///
pub fn clear(self: *Self) void {
self.values = self.values[0 .. 0];
}
///
/// Deinitializes `self` and sets it to an invalid state, freeing all memory allocated by `allocator`.
///
/// To clear all items from the stack while preserving the current internal buffer, see [clear] instead.
///
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
/// `self`.
///
pub fn deinit(self: *Self, allocator: io.Allocator) void {
io.deallocate(allocator, self.values);
self.capacity = 0;
}
///
/// Attempts to remove `amount` number of `Element`s from the stack, returning `bool` if it was successful,
/// otherwise `false` if the stack contains fewer elements than `amount`.
///
pub fn drop(self: *Self, amount: usize) bool {
if (amount > self.values.len) return false;
self.values = self.values[0 .. self.values.len - amount];
return true;
}
///
/// Attempts to grow the internal buffer of `self` by `growth_amount` using `allocator`.
///
/// The function returns [AllocatorError] instead if `allocator` cannot commit the memory required to grow the
/// internal buffer by `growth_amount`, leaving `self` in the same state that it was in prior to starting the
/// grow.
///
/// Growing ahead of pushing operations is useful when the upper bound of pushes is well-understood, as it can
/// reduce the number of allocations required per push.
///
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
/// `self`.
///
pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
const grown_capacity = self.capacity + growth_amount;
const values = (try io.allocate_many(Element, grown_capacity, allocator))[0 .. self.values.len];
errdefer io.deallocate(allocator, values);
{
var index: usize = 0;
while (index < self.values.len) {
values[index] = self.values[index];
}
}
io.deallocate(allocator, self.values);
self.values = values;
self.capacity = grown_capacity;
}
///
/// Attempts to allocate and return an empty stack with an internal buffer of `initial_capacity` size using
/// `allocator` as the memory allocation strategy.
///
/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required for an
/// internal buffer of `initial_capacity` size.
///
pub fn init(allocator: io.Allocator, initial_capacity: usize) !Self {
const values = try io.allocate_many(Element, initial_capacity, allocator);
errdefer io.deallocate(values);
return Self{
.capacity = initial_capacity,
.values = values[0 .. 0],
};
}
///
/// Attempts to push every `Element` in `values` to `self` using `allocator` to grow the internal buffer as
/// necessary.
///
/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required to grow the
/// internal buffer of `self` when necessary.
///
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
/// `self`.
///
pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Element) io.AllocationError!void {
const new_length = self.values.len + values.len;
if (new_length >= self.capacity) {
try self.grow(allocator, math.min(new_length, self.capacity));
}
const offset_index = self.values.len;
self.values = self.values.ptr[0 .. new_length];
{
var index: usize = 0;
while (index < values.len) : (index += 1) self.values[offset_index + index] = values[index];
}
}
///
/// Attempts to push the `Element` in `value` to `self` by `amount` number of times using `allocator` to grow
/// the internal buffer as necessary.
///
/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required to grow the
/// internal buffer of `self` when necessary.
///
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
/// `self`.
///
pub fn push_many(self: *Self, allocator: io.Allocator, value: Element, amount: usize) io.AllocationError!void {
const new_length = self.values.len + amount;
if (new_length >= self.capacity) {
try self.grow(allocator, math.max(usize, new_length, self.capacity));
}
const offset_index = self.values.len;
self.values = self.values.ptr[0 .. new_length];
{
var index: usize = 0;
while (index < amount) : (index += 1) self.values[offset_index + index] = value;
}
}
///
/// Attempts to push the `Element` in `value` to `self` using `allocator` to grow the internal buffer as
/// necessary.
///
/// The function returns [AllocationError] instead if `allocator` cannot commit the memory required to grow the
/// internal buffer of `self` when necessary.
///
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize
/// `self`.
///
pub fn push_one(self: *Self, allocator: io.Allocator, value: Element) io.AllocationError!void {
if (self.values.len == self.capacity) {
try self.grow(allocator, math.max(1, self.capacity));
}
const offset_index = self.values.len;
self.values = self.values.ptr[0 .. self.values.len + 1];
self.values[offset_index] = value;
}
};
}
///
/// Binds `stack` to a [io.Allocator], returning it.
///
pub fn stack_as_allocator(stack: *Stack(u8)) io.Allocator {
return io.Allocator.bind(stack, struct {
pub fn reallocate(writable_stack: *Stack(u8), existing_allocation: ?[*]u8, allocation_size: usize) ?[*]u8 {
if (allocation_size == 0) return null;
writable_stack.push_all(io.bytes_of(&allocation_size)) catch return null;
const usize_size = @sizeOf(usize);
errdefer debug.assert(writable_stack.drop(usize_size));
const allocation_index = writable_stack.values.len;
if (existing_allocation) |allocation| {
const existing_allocation_size = @intToPtr(*const usize, @ptrToInt(allocation) - usize_size).*;
writable_stack.push_all(allocation[0 .. existing_allocation_size]) catch return null;
} else {
writable_stack.push_many(0, allocation_size) catch return null;
}
return @ptrCast([*]u8, &writable_stack.values[allocation_index]);
}
});
}
///
/// Binds `stack` to a [io.Writer], returning it.
///
pub fn stack_as_writer(stack: *Stack(u8)) io.Writer {
return io.Writer.bind(stack, struct {
pub fn write(writable_stack: *Stack(u8), buffer: []const u8) io.WriteError!usize {
writable_stack.push_all(buffer) catch |grow_error| switch (grow_error) {
error.OutOfMemory => return error.IoUnavailable,
};
return buffer.len;
}
});
}