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), }; } };