ona/source/coral/list.zig

230 lines
7.5 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 `Value`.
///
pub fn Stack(comptime Value: type) type {
return struct {
capacity: usize = 0,
values: []Value = &.{},
///
/// Stack type.
///
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* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
/// strategy as the one originally used to allocate the current internal buffer.
///
pub fn deinit(self: *Self, allocator: io.Allocator) void {
if (self.capacity == 0) {
return;
}
io.deallocate(allocator, self.values.ptr[0 .. self.capacity]);
self.values = &.{};
self.capacity = 0;
}
///
/// Attempts to remove `amount` number of `Value`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 [io.AllocatorError] if `allocator` could not 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 multiple push operations is useful when the upper bound of pushes is well-understood, as it
/// can reduce the number of allocations required per push.
///
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
/// strategy as the one originally used to allocate the current internal buffer.
///
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(allocator, grown_capacity, Value))[0 .. self.values.len];
errdefer io.deallocate(allocator, values);
if (self.capacity != 0) {
for (0 .. self.values.len) |index| {
values[index] = self.values[index];
}
io.deallocate(allocator, self.values.ptr[0 .. self.capacity]);
}
self.values = values;
self.capacity = grown_capacity;
}
///
/// Attempts to remove the last element of `self` that was inserted, if one exists, returning it or `null` if
/// `self` is empty.
///
pub fn pop(self: *Self) ?Value {
if (self.values.len == 0) {
return null;
}
const last_index = self.values.len - 1;
defer self.values = self.values[0 .. last_index];
return self.values[last_index];
}
///
/// Attempts to push every `Value` in `values` to `self` using `allocator` to grow the internal buffer as
/// necessary.
///
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
/// internal buffer of `self` when necessary.
///
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
/// strategy as the one originally used to allocate the current internal buffer.
///
pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Value) io.AllocationError!void {
const new_length = self.values.len + values.len;
if (new_length > self.capacity) {
try self.grow(allocator, values.len + values.len);
}
const offset_index = self.values.len;
self.values = self.values.ptr[0 .. new_length];
for (0 .. values.len) |index| {
self.values[offset_index + index] = values[index];
}
}
///
/// Attempts to push the `Value` in `value` to `self` by `amount` number of times using `allocator` to grow
/// the internal buffer as necessary.
///
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
/// internal buffer of `self` when necessary.
///
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
/// strategy as the one originally used to allocate the current internal buffer.
///
pub fn push_many(self: *Self, allocator: io.Allocator, value: Value, amount: usize) io.AllocationError!void {
const new_length = self.values.len + amount;
if (new_length >= self.capacity) {
try self.grow(allocator, amount + amount);
}
const offset_index = self.values.len;
self.values = self.values.ptr[0 .. new_length];
for (0 .. amount) |index| {
self.values[offset_index + index] = value;
}
}
///
/// Attempts to push the `Value` in `value` to `self` using `allocator` to grow the internal buffer as
/// necessary.
///
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the
/// internal buffer of `self` when necessary.
///
/// *Note* if the `capacity` field of `self` is a non-zero value, `allocator` must reference the same allocation
/// strategy as the one originally used to allocate the current internal buffer.
///
pub fn push_one(self: *Self, allocator: io.Allocator, value: Value) 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;
}
};
}
///
/// Bridge context between a list type implement as part of the list module and an allocator, allowing the list resource
/// referenced by the [Writable] instance to be written to directly or virtually via the [io.Writer] interface.
///
/// *Note* if the given list contains an existing allocation, the provided [io.Allocator] instance must reference the
/// same allocation strategy as the one originally used to allocate the list type memory.
///
pub const Writable = struct {
allocator: io.Allocator,
list: union (enum) {
stack: *ByteStack,
},
///
/// Stack of bytes.
///
const ByteStack = Stack(u8);
///
/// Returns a [io.Writer] instance that binds a reference of `self` to the [write] operation.
///
pub fn as_writer(self: *Writable) io.Writer {
return io.Writer.bind(Writable, self, struct {
fn write(writable: *Writable, bytes: []const u8) ?usize {
writable.write(bytes) catch return null;
return bytes.len;
}
}.write);
}
///
/// Attempts to call the appropriate multi-element writing function for the current list referenced by `self`,
/// passing `bytes` along.
///
/// The function returns [io.AllocationError] if `allocator` could not commit the memory by the list implementation
/// referenced by `self`. See the specific implementation details of the respective list type for more information.
///
pub fn write(self: *Writable, bytes: []const u8) io.AllocationError!void {
return switch (self.list) {
.stack => |stack| stack.push_all(self.allocator, bytes),
};
}
};