Tidy up coral library
This commit is contained in:
parent
d5d5b69f54
commit
54e716e4cf
|
@ -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();
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]);
|
|
||||||
}
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue