265 lines
8.0 KiB
Zig
Executable File
265 lines
8.0 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,
|
|
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* `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 `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 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(Value, grown_capacity, allocator))[0 .. self.values.len];
|
|
|
|
errdefer io.deallocate(allocator, values);
|
|
|
|
for (0 .. self.values.len) |index| {
|
|
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 [io.AllocationError] if `allocator` could not 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(Value, initial_capacity, allocator);
|
|
|
|
errdefer io.deallocate(values);
|
|
|
|
return Self{
|
|
.capacity = initial_capacity,
|
|
.values = values[0 .. 0],
|
|
};
|
|
}
|
|
|
|
///
|
|
/// 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* `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 Value) 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];
|
|
|
|
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* `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: Value, 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];
|
|
|
|
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* `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: 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;
|
|
}
|
|
};
|
|
}
|
|
|
|
///
|
|
/// Generic, byte-writable interface for all list types supported by the module.
|
|
///
|
|
/// As the type is only a thin wrapper around other resources, it does not manage any memory nor is it permitted to
|
|
/// outlive the resources it references.
|
|
///
|
|
pub const Writable = struct {
|
|
allocator: io.Allocator,
|
|
|
|
list: union (enum) {
|
|
stack: *ByteStack,
|
|
},
|
|
|
|
///
|
|
/// Stack of bytes.
|
|
///
|
|
const ByteStack = Stack(u8);
|
|
|
|
///
|
|
/// Binds and returns `self` as a [io.Writer].
|
|
///
|
|
pub fn as_writer(self: *Writable) io.Writer {
|
|
return io.Writer.bind(Writable, self, struct {
|
|
fn write(writable: *Writable, buffer: []const u8) ?usize {
|
|
writable.write(buffer) catch |allocation_error| switch (allocation_error) {
|
|
error.OutOfMemory => return null,
|
|
};
|
|
|
|
return buffer.len;
|
|
}
|
|
}.write);
|
|
}
|
|
|
|
///
|
|
/// Returns a new [Writable] from wrapping `stack` and `allocator`.
|
|
///
|
|
/// *Note* `allocator` must reference the same allocation strategy as the one originally used to initialize `stack`.
|
|
///
|
|
pub fn from_stack(allocator: io.Allocator, stack: *ByteStack) Writable {
|
|
return .{
|
|
.allocator = allocator,
|
|
.list = .{.stack = stack},
|
|
};
|
|
}
|
|
|
|
///
|
|
/// Attempts to write the singular `byte` to the list referenced by `self`.
|
|
///
|
|
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required by the internal
|
|
/// list.
|
|
///
|
|
pub fn put(self: *Writable, byte: u8) io.AllocationError!void {
|
|
try switch (self.list) {
|
|
.stack => |stack| stack.push_one(self.allocator, byte),
|
|
};
|
|
}
|
|
|
|
///
|
|
/// Attempst to write all of `bytes` to the list referenced by `self`.
|
|
///
|
|
/// The function returns [io.AllocationError] if `allocator` could not commit the memory required by the internal
|
|
/// list.
|
|
///
|
|
pub fn write(self: *Writable, bytes: []const u8) io.AllocationError!void {
|
|
try switch (self.list) {
|
|
.stack => |stack| stack.push_all(self.allocator, bytes),
|
|
};
|
|
}
|
|
};
|