Tidy up coral library

This commit is contained in:
kayomn 2023-05-23 00:08:34 +00:00
parent d5d5b69f54
commit 54e716e4cf
10 changed files with 494 additions and 295 deletions

View File

@ -1,69 +0,0 @@
const io = @import("./io.zig");
const math = @import("./math.zig");
pub const Fixed = struct {
data: []u8,
write_index: usize = 0,
pub fn as_writer(self: *Fixed) io.Writer {
return io.Writer.bind(self, struct {
fn fallible_write(fixed: *Fixed, buffer: []const u8) io.WriteError!usize {
return fixed.write(buffer);
}
}.fallible_write);
}
pub fn remaining(self: Fixed) usize {
return self.data.len - self.write_index;
}
pub fn is_empty(self: Fixed) bool {
return !self.is_full();
}
pub fn is_full(self: Fixed) bool {
return self.write_index == self.data.len;
}
pub fn write(self: *Fixed, buffer: []const u8) usize {
const range = math.min(usize, buffer.len, self.remaining());
io.copy(self.data[self.write_index ..], buffer[0 .. range]);
self.write_index += range;
return range;
}
};
pub const Ring = struct {
data: []u8,
read_index: usize = 0,
write_index: usize = 0,
pub fn as_reader(self: *Ring) io.Reader {
return io.Reader.bind(self, Ring);
}
pub fn as_writer(self: *Ring) io.Writer {
return io.Writer.bind(self, Ring);
}
pub fn filled(self: Ring) usize {
return (self.write_index + (2 * self.data.len *
@boolToInt(self.write_index < self.read_index))) - self.read_index;
}
pub fn is_empty(self: Ring) bool {
return self.write_index == self.read_index;
}
pub fn is_full(self: Ring) bool {
return ((self.write_index + self.data.len) % (self.data.len * 2)) != self.read_index;
}
pub fn remaining(self: Ring) usize {
return self.data.len - self.filled();
}
};

View File

@ -1,19 +1,44 @@
pub const buffer = @import("./buffer.zig"); ///
/// Debug build-only utilities and sanity-checkers.
///
pub const debug = @import("./debug.zig"); pub const debug = @import("./debug.zig");
pub const format = @import("./format.zig"); ///
/// Heap memory allocation strategies.
///
pub const heap = @import("./heap.zig"); pub const heap = @import("./heap.zig");
///
/// Platform-agnostic data input and output operations.
///
pub const io = @import("./io.zig"); pub const io = @import("./io.zig");
///
/// Data structures and utilities for sequential, "list-like" collections.
///
pub const list = @import("./list.zig");
///
/// Types and functions designed for mathematics in interactive media applications.
///
pub const math = @import("./math.zig"); pub const math = @import("./math.zig");
///
/// Data structures and utilities for fragmented, "heap-like" collections.
///
pub const slab = @import("./slab.zig");
///
/// Data structures and utilities for the highly-specialized "slotmap" collection.
///
pub const slots = @import("./slots.zig"); pub const slots = @import("./slots.zig");
pub const stack = @import("./stack.zig"); ///
/// Data structures and utilities for associative, "table-like" collections.
///
pub const table = @import("./table.zig"); pub const table = @import("./table.zig");
///
/// Converters, parsers, and validators for sequences of bytes treated as UTF8 unicode strings.
///
pub const utf8 = @import("./utf8.zig"); pub const utf8 = @import("./utf8.zig");

View File

@ -1,4 +1,10 @@
///
/// Active code comment to assert that `condition` should always be true.
///
/// Safety-checked behavior is invoked where `condition` evaluates to false.
///
pub fn assert(condition: bool) void { pub fn assert(condition: bool) void {
if (!condition) unreachable; if (!condition) {
unreachable;
}
} }

View File

@ -1,46 +0,0 @@
const io = @import("./io.zig");
pub const Value = union(enum) {
newline: void,
string: []const u8,
unsigned: u128,
signed: i128,
};
pub fn print(writer: io.Writer, values: []const Value) io.WriteError!usize {
var written: usize = 0;
for (values) |value| written += switch (value) {
.newline => try writer.invoke("\n"),
.string => |string| try writer.invoke(string),
.unsigned => |unsigned| try print_unsigned(writer, unsigned),
};
return written;
}
pub fn print_unsigned(writer: io.Writer, value: u128) io.WriteError!usize {
if (value == 0) return writer.invoke("0");
var buffer = [_]u8{0} ** 39;
var buffer_count: usize = 0;
var split_value = value;
while (split_value != 0) : (buffer_count += 1) {
const radix = 10;
buffer[buffer_count] = @intCast(u8, (split_value % radix) + '0');
split_value = (split_value / radix);
}
{
const half_buffer_count = buffer_count / 2;
var index: usize = 0;
while (index < half_buffer_count) : (index += 1) {
io.swap(u8, &buffer[index], &buffer[buffer_count - index - 1]);
}
}
return writer.invoke(buffer[0 .. buffer_count]);
}

View File

@ -13,6 +13,9 @@ pub const AllocationOptions = struct {
pub const Allocator = Functor(?[]u8, AllocationOptions); pub const Allocator = Functor(?[]u8, AllocationOptions);
///
/// Function pointer coupled with a state context for providing dynamic dispatch over a given `Input` and `Output`.
///
pub fn Functor(comptime Output: type, comptime Input: type) type { pub fn Functor(comptime Output: type, comptime Input: type) type {
return struct { return struct {
context: *anyopaque, context: *anyopaque,
@ -20,20 +23,19 @@ pub fn Functor(comptime Output: type, comptime Input: type) type {
const Self = @This(); const Self = @This();
pub fn bind(state: anytype, comptime invoker: fn (capture: @TypeOf(state), input: Input) Output) Self { pub fn bind(comptime State: type, state: *State, comptime invoker: fn (capture: *State, input: Input) Output) Self {
const State = @TypeOf(state);
const state_info = @typeInfo(State);
if (state_info != .Pointer) {
@compileError("`@typeOf(state)` must be a pointer type");
}
return .{ return .{
.context = state, .context = state,
.invoker = struct { .invoker = struct {
fn invoke_opaque(context: *anyopaque, input: Input) Output { fn invoke_opaque(context: *anyopaque, input: Input) Output {
return invoker(@ptrCast(State, @alignCast(@alignOf(state_info.Pointer.child), context)), input); const state_alignment = @alignOf(State);
if (state_alignment == 0) {
return invoker(@ptrCast(*State, context), input);
}
return invoker(@ptrCast(*State, @alignCast(state_alignment, context)), input);
} }
}.invoke_opaque, }.invoke_opaque,
}; };
@ -59,6 +61,39 @@ pub fn Tag(comptime Element: type) type {
}; };
} }
pub const WritableMemory = struct {
slice: []u8,
pub fn as_writer(self: *WritableMemory) Writer {
return Writer.bind(self, struct {
fn write(writable_memory: *WritableMemory, data: []const u8) WriteError!usize {
return writable_memory.write(data);
}
}.write);
}
pub fn put(self: *WritableMemory, byte: u8) bool {
if (self.slice.len == 0) {
return false;
}
self.slice[0] = byte;
self.slice = self.slice[1 ..];
return true;
}
pub fn write(self: *WritableMemory, bytes: []const u8) usize {
const writable = math.min(self.slice.len, bytes.len);
copy(self.slice, bytes);
self.slice = self.slice[writable ..];
return writable;
}
};
pub const WriteError = error{ pub const WriteError = error{
IoUnavailable, IoUnavailable,
}; };
@ -98,13 +133,15 @@ pub fn bytes_of(value: anytype) []const u8 {
} }
pub fn compare(this: []const u8, that: []const u8) isize { pub fn compare(this: []const u8, that: []const u8) isize {
const range = math.min(usize, this.len, that.len); const range = math.min(this.len, that.len);
var index: usize = 0; var index: usize = 0;
while (index < range) : (index += 1) { while (index < range) : (index += 1) {
const difference = @intCast(isize, this[index]) - @intCast(isize, that[index]); const difference = @intCast(isize, this[index]) - @intCast(isize, that[index]);
if (difference != 0) return difference; if (difference != 0) {
return difference;
}
} }
return @intCast(isize, this.len) - @intCast(isize, that.len); return @intCast(isize, this.len) - @intCast(isize, that.len);
@ -132,6 +169,18 @@ pub fn deallocate(allocator: Allocator, allocation: anytype) void {
} }
} }
pub fn bytes_to(comptime Type: type, source_bytes: []const u8) ?Type {
const type_size = @sizeOf(Type);
if (source_bytes.len != type_size) return null;
var target_bytes = @as([type_size]u8, undefined);
copy(&target_bytes, source_bytes);
return @bitCast(Type, target_bytes);
}
pub fn copy(target: []u8, source: []const u8) void { pub fn copy(target: []u8, source: []const u8) void {
var index: usize = 0; var index: usize = 0;
@ -185,18 +234,6 @@ pub fn overlaps(pointer: [*]u8, memory_range: []u8) bool {
return (pointer >= memory_range.ptr) and (pointer < (memory_range.ptr + memory_range.len)); return (pointer >= memory_range.ptr) and (pointer < (memory_range.ptr + memory_range.len));
} }
pub fn bytes_to(comptime Type: type, source_bytes: []const u8) ?Type {
const type_size = @sizeOf(Type);
if (source_bytes.len != type_size) return null;
var target_bytes = @as([type_size]u8, undefined);
copy(&target_bytes, source_bytes);
return @bitCast(Type, target_bytes);
}
pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) AllocationError![]@typeInfo(@TypeOf(allocation)).Pointer.child { pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) AllocationError![]@typeInfo(@TypeOf(allocation)).Pointer.child {
const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer; const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer;
const Element = pointer_info.child; const Element = pointer_info.child;
@ -211,12 +248,12 @@ pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) Allo
}) orelse return error.OutOfMemory).ptr))[0 .. amount]; }) orelse return error.OutOfMemory).ptr))[0 .. amount];
} }
pub fn slice_sentineled(comptime element: type, comptime sentinel: element, sequence: [*:sentinel]const element) []const element { pub fn sentinel_index(comptime element: type, comptime sentinel: element, sequence: [*:sentinel]const element) usize {
var length: usize = 0; var index: usize = 0;
while (sequence[length] != sentinel) : (length += 1) {} while (sequence[index] != sentinel) : (index += 1) {}
return sequence[0..length]; return index;
} }
pub fn stream(output: Writer, input: Reader, buffer: []u8) (ReadError || WriteError)!u64 { pub fn stream(output: Writer, input: Reader, buffer: []u8) (ReadError || WriteError)!u64 {

225
source/coral/list.zig Executable file
View File

@ -0,0 +1,225 @@
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;
}
});
}

View File

@ -1,3 +1,8 @@
const std = @import("std");
///
/// Errors that may occur during checked integer arithmetic operations.
///
pub const CheckedArithmeticError = error { pub const CheckedArithmeticError = error {
IntOverflow, IntOverflow,
}; };
@ -20,54 +25,139 @@ pub fn Unsigned(comptime bits: comptime_int) type {
}}); }});
} }
///
/// Two-dimensional vector type.
///
pub const Vector2 = extern struct { pub const Vector2 = extern struct {
x: f32, x: f32,
y: f32, y: f32,
///
/// A [Vector2] with a value of `0` assigned to all of the components.
///
pub const zero = Vector2{.x = 0, .y = 0}; pub const zero = Vector2{.x = 0, .y = 0};
}; };
///
/// Attempts to perform a checked addition between `a` and `b`, returning the result or [CheckedArithmeticError] if the
/// operation tried to invoke safety-checked behavior.
///
/// `checked_add` can be seen as an alternative to the language-native addition operator (+) that exposes the safety-
/// checked behavior in the form of an error type that may be caught or tried on.
///
pub fn checked_add(a: anytype, b: anytype) CheckedArithmeticError!@TypeOf(a + b) { pub fn checked_add(a: anytype, b: anytype) CheckedArithmeticError!@TypeOf(a + b) {
const result = @addWithOverflow(a, b); const result = @addWithOverflow(a, b);
if (result.@"1" != 0) return error.IntOverflow; if (result.@"1" != 0) {
return error.IntOverflow;
}
return result.@"0"; return result.@"0";
} }
///
/// Attempts to perform a checked integer cast to `Int` on `value`, returning the result or [CheckedArithmeticError] if
/// the operation tried to invoke safety-checked behavior.
///
/// `checked_cast` can be seen as an alternative to the language-native `@intCast` builtin that exposes the safety-
/// checked behavior in the form of an error type that may be caught or tried on.
///
pub fn checked_cast(comptime Int: type, value: anytype) CheckedArithmeticError!Int {
const int_type_info = @typeInfo(Int);
if (int_type_info != .Int) {
@compileError("`Int` must be of type int");
}
if ((value < min_int(int_type_info.Int)) or (value > max_int(int_type_info.Int))) {
return error.IntOverflow;
}
return @intCast(Int, value);
}
///
/// Attempts to perform a checked multiplication between `a` and `b`, returning the result or [CheckedArithmeticError]
/// if the operation tried to invoke safety-checked behavior.
///
/// `checked_mul` can be seen as an alternative to the language-native multiplication operator (*) that exposes the
/// safety-checked behavior in the form of an error type that may be caught or tried on.
///
pub fn checked_mul(a: anytype, b: anytype) CheckedArithmeticError!@TypeOf(a * b) { pub fn checked_mul(a: anytype, b: anytype) CheckedArithmeticError!@TypeOf(a * b) {
const result = @mulWithOverflow(a, b); const result = @mulWithOverflow(a, b);
if (result.@"1" != 0) return error.IntOverflow; if (result.@"1" != 0) {
return error.IntOverflow;
}
return result.@"0"; return result.@"0";
} }
///
/// Attempts to perform a checked subtraction between `a` and `b`, returning the result or [CheckedArithmeticError] if
/// the operation tried to invoke safety-checked behavior.
///
/// `checked_sub` can be seen as an alternative to the language-native subtraction operator (-) that exposes the safety-
/// checked behavior in the form of an error type that may be caught or tried on.
///
pub fn checked_sub(a: anytype, b: anytype) CheckedArithmeticError!@TypeOf(a - b) { pub fn checked_sub(a: anytype, b: anytype) CheckedArithmeticError!@TypeOf(a - b) {
const result = @subWithOverflow(a, b); const result = @subWithOverflow(a, b);
if (result.@"1" != 0) return error.IntOverflow; if (result.@"1" != 0) {
return error.IntOverflow;
}
return result.@"0"; return result.@"0";
} }
pub fn clamp(comptime Scalar: type, value: Scalar, min_value: Scalar, max_value: Scalar) Scalar { ///
return max(Scalar, min_value, min(Scalar, max_value, value)); /// Returns `value` clamped between the inclusive bounds of `lower` and `upper`.
///
pub fn clamp(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lower, upper) {
return max(lower, min(upper, value));
} }
pub fn max(comptime Scalar: type, this: Scalar, that: Scalar) Scalar { ///
return @max(this, that); /// Returns `true` if `value` is clamped within the inclusive bounds of `lower` and `upper`.
///
pub fn is_clamped(value: anytype, lower: anytype, upper: anytype) bool {
return (value >= lower) and (value <= upper);
} }
pub fn max_int(comptime Int: type) Int { ///
const info = @typeInfo(Int); /// Returns the maximum value between `a` and `b`.
const bit_count = info.Int.bits; ///
pub fn max(a: anytype, b: anytype) @TypeOf(a, b) {
return @max(a, b);
}
///
/// Returns the maximum value that the integer described by `int` may express.
///
pub fn max_int(comptime int: std.builtin.Type.Int) comptime_int {
const bit_count = int.bits;
if (bit_count == 0) return 0; if (bit_count == 0) return 0;
return (1 << (bit_count - @boolToInt(info.Int.signedness == .signed))) - 1; return (1 << (bit_count - @boolToInt(int.signedness == .signed))) - 1;
} }
pub fn min(comptime Scalar: type, this: Scalar, that: Scalar) Scalar { ///
return @min(this, that); /// Returns the minimum value between `a` and `b`.
///
pub fn min(a: anytype, b: anytype) @TypeOf(a, b) {
return @min(a, b);
}
///
/// Returns the minimum value that the integer described by `int` may express.
///
pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int {
if (int.signedness == .unsigned) return 0;
const bit_count = int.bits;
if (bit_count == 0) return 0;
return -(1 << (bit_count - 1));
} }

View File

@ -4,22 +4,20 @@ const io = @import("./io.zig");
const math = @import("./math.zig"); const math = @import("./math.zig");
const stack = @import("./stack.zig");
/// ///
/// Retruns a set of dense slots that may store `Element`s indexable by a [Slot], where `key` defines how many bits the /// Retruns a dense mapping slots that may store `Element`s indexable by a [Slot], where `key` defines how many bits the
/// [Slot] used is made from. /// [Slot] used is made from.
/// ///
pub fn Dense(comptime key: Key, comptime Element: type) type { pub fn Map(comptime key: Key, comptime Element: type) type {
const KeySlot = Slot(key); const KeySlot = Slot(key);
const Index = math.Unsigned(key.index_bits); const Index = math.Unsigned(key.index_bits);
return struct { return struct {
capacity: usize = 0, capacity: usize,
values: []Element = &.{}, values: []Element,
slots: ?[*]KeySlot = null, slots: [*]KeySlot,
erase: ?[*]Index = null, erase: [*]Index,
next_free: Index = 0, next_free: Index,
const Self = @This(); const Self = @This();
@ -120,6 +118,34 @@ pub fn Dense(comptime key: Key, comptime Element: type) type {
} }
} }
///
/// Attempts to return an initialized slot map with an initial capacity of `initial_capacity` and `allocator` as
/// the memory allocation strategy.
///
/// Upon failure, a [io.AllocationError] is returned instead.
///
pub fn init(allocator: io.Allocator, initial_capacity: usize) io.AllocationError!Self {
const values = try io.allocate_many(Element, initial_capacity, allocator);
errdefer io.deallocate(allocator, values);
const slots = try io.allocate_many(KeySlot, initial_capacity, allocator);
errdefer io.deallocate(allocator, slots);
const erase = try io.allocate_many(Index, initial_capacity, allocator);
errdefer io.deallocate(allocator, erase);
return Self{
.capacity = initial_capacity,
.values = values[0 .. 0],
.slots = slots.ptr,
.erase = erase.ptr,
.next_free = 0,
};
}
/// ///
/// Attempts to insert `value` into `self`, growing the internal buffer with `allocator` if it is full and /// Attempts to insert `value` into `self`, growing the internal buffer with `allocator` if it is full and
/// returning a `Slot` of `key` referencing the inserted element or a [io.AllocationError] if it failed. /// returning a `Slot` of `key` referencing the inserted element or a [io.AllocationError] if it failed.

View File

@ -1,123 +0,0 @@
const debug = @import("./debug.zig");
const io = @import("./io.zig");
const math = @import("./math.zig");
pub fn Dense(comptime Element: type) type {
return struct {
capacity: usize = 0,
values: []Element = &.{},
const Self = @This();
pub fn clear(self: *Self) void {
self.values = self.values[0 .. 0];
}
pub fn deinit(self: *Self, allocator: io.Allocator) void {
io.deallocate(allocator, self.values);
}
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;
}
pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
const grown_capacity = self.capacity + growth_amount;
self.values = (try io.reallocate(allocator, self.values, grown_capacity))[0 .. self.values.len];
self.capacity = grown_capacity;
}
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(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 < values.len) : (index += 1) self.values[offset_index + index] = values[index];
}
}
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;
}
}
pub fn push_one(self: *Self, allocator: io.MemoryAllocator, value: Element) io.AllocationError!void {
if (self.values.len == self.capacity) {
try self.grow(allocator, math.max(usize, 1, self.capacity));
}
const offset_index = self.values.len;
self.values = self.values.ptr[0 .. self.values.len + 1];
self.values[offset_index] = value;
}
};
}
pub fn as_dense_allocator(stack: *Dense(u8)) io.Allocator {
return io.Allocator.bind(stack, struct {
pub fn reallocate(writable_stack: *Dense(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]);
}
});
}
pub fn as_dense_writer(stack: *Dense(u8)) io.Writer {
return io.Writer.bind(stack, struct {
pub fn write(writable_stack: *Dense(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;
}
});
}

View File

@ -1,3 +1,5 @@
const io = @import("./io.zig");
const math = @import("./math.zig"); const math = @import("./math.zig");
pub const IntParseError = math.CheckedArithmeticError || ParseError; pub const IntParseError = math.CheckedArithmeticError || ParseError;
@ -99,3 +101,29 @@ pub fn parse_unsigned(comptime bits: comptime_int, utf8: []const u8) IntParseErr
return result; return result;
} }
pub fn print_unsigned(comptime bit_size: comptime_int, writer: io.Writer, value: math.Unsigned(bit_size)) io.WriteError!usize {
if (value == 0) return writer.invoke("0");
var buffer = [_]u8{0} ** 39;
var buffer_count: usize = 0;
var split_value = value;
while (split_value != 0) : (buffer_count += 1) {
const radix = 10;
buffer[buffer_count] = @intCast(u8, (split_value % radix) + '0');
split_value = (split_value / radix);
}
{
const half_buffer_count = buffer_count / 2;
var index: usize = 0;
while (index < half_buffer_count) : (index += 1) {
io.swap(u8, &buffer[index], &buffer[buffer_count - index - 1]);
}
}
return writer.invoke(buffer[0 .. buffer_count]);
}