From 54e716e4cfcfa36f86ee6222f47119ab1d74bfff Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 23 May 2023 00:08:34 +0000 Subject: [PATCH] Tidy up coral library --- source/coral/buffer.zig | 69 ------------ source/coral/coral.zig | 37 +++++-- source/coral/debug.zig | 10 +- source/coral/format.zig | 46 -------- source/coral/io.zig | 91 +++++++++++----- source/coral/list.zig | 225 ++++++++++++++++++++++++++++++++++++++++ source/coral/math.zig | 116 ++++++++++++++++++--- source/coral/slots.zig | 44 ++++++-- source/coral/stack.zig | 123 ---------------------- source/coral/utf8.zig | 28 +++++ 10 files changed, 494 insertions(+), 295 deletions(-) delete mode 100755 source/coral/buffer.zig delete mode 100755 source/coral/format.zig create mode 100755 source/coral/list.zig delete mode 100755 source/coral/stack.zig diff --git a/source/coral/buffer.zig b/source/coral/buffer.zig deleted file mode 100755 index 401931a..0000000 --- a/source/coral/buffer.zig +++ /dev/null @@ -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(); - } -}; diff --git a/source/coral/coral.zig b/source/coral/coral.zig index 9c3aa81..9bb6c92 100755 --- a/source/coral/coral.zig +++ b/source/coral/coral.zig @@ -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 format = @import("./format.zig"); - +/// +/// Heap memory allocation strategies. +/// pub const heap = @import("./heap.zig"); +/// +/// Platform-agnostic data input and output operations. +/// 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"); +/// +/// 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 stack = @import("./stack.zig"); - +/// +/// Data structures and utilities for associative, "table-like" collections. +/// 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"); diff --git a/source/coral/debug.zig b/source/coral/debug.zig index d6dd2a6..b38082e 100755 --- a/source/coral/debug.zig +++ b/source/coral/debug.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 { - if (!condition) unreachable; + if (!condition) { + unreachable; + } } diff --git a/source/coral/format.zig b/source/coral/format.zig deleted file mode 100755 index 153f9ad..0000000 --- a/source/coral/format.zig +++ /dev/null @@ -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]); -} diff --git a/source/coral/io.zig b/source/coral/io.zig index d52284f..3b4a001 100755 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -13,6 +13,9 @@ pub const AllocationOptions = struct { 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 { return struct { context: *anyopaque, @@ -20,20 +23,19 @@ pub fn Functor(comptime Output: type, comptime Input: type) type { const Self = @This(); - pub fn bind(state: anytype, comptime invoker: fn (capture: @TypeOf(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"); - } - + pub fn bind(comptime State: type, state: *State, comptime invoker: fn (capture: *State, input: Input) Output) Self { return .{ .context = state, .invoker = struct { 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, }; @@ -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{ IoUnavailable, }; @@ -98,13 +133,15 @@ pub fn bytes_of(value: anytype) []const u8 { } 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; while (index < range) : (index += 1) { 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); @@ -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 { 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)); } -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 { const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer; 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]; } -pub fn slice_sentineled(comptime element: type, comptime sentinel: element, sequence: [*:sentinel]const element) []const element { - var length: usize = 0; +pub fn sentinel_index(comptime element: type, comptime sentinel: element, sequence: [*:sentinel]const element) usize { + 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 { diff --git a/source/coral/list.zig b/source/coral/list.zig new file mode 100755 index 0000000..942afbb --- /dev/null +++ b/source/coral/list.zig @@ -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; + } + }); +} diff --git a/source/coral/math.zig b/source/coral/math.zig index 51f1f41..cd843f0 100755 --- a/source/coral/math.zig +++ b/source/coral/math.zig @@ -1,3 +1,8 @@ +const std = @import("std"); + +/// +/// Errors that may occur during checked integer arithmetic operations. +/// pub const CheckedArithmeticError = error { IntOverflow, }; @@ -20,54 +25,139 @@ pub fn Unsigned(comptime bits: comptime_int) type { }}); } +/// +/// Two-dimensional vector type. +/// pub const Vector2 = extern struct { x: f32, y: f32, + /// + /// A [Vector2] with a value of `0` assigned to all of the components. + /// 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) { const result = @addWithOverflow(a, b); - if (result.@"1" != 0) return error.IntOverflow; + if (result.@"1" != 0) { + return error.IntOverflow; + } 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) { const result = @mulWithOverflow(a, b); - if (result.@"1" != 0) return error.IntOverflow; + if (result.@"1" != 0) { + return error.IntOverflow; + } 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) { const result = @subWithOverflow(a, b); - if (result.@"1" != 0) return error.IntOverflow; + if (result.@"1" != 0) { + return error.IntOverflow; + } 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); - const bit_count = info.Int.bits; +/// +/// Returns the maximum value between `a` and `b`. +/// +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; - 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)); } diff --git a/source/coral/slots.zig b/source/coral/slots.zig index 22cfb24..3518ba4 100755 --- a/source/coral/slots.zig +++ b/source/coral/slots.zig @@ -4,22 +4,20 @@ const io = @import("./io.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. /// -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 Index = math.Unsigned(key.index_bits); return struct { - capacity: usize = 0, - values: []Element = &.{}, - slots: ?[*]KeySlot = null, - erase: ?[*]Index = null, - next_free: Index = 0, + capacity: usize, + values: []Element, + slots: [*]KeySlot, + erase: [*]Index, + next_free: Index, 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 /// returning a `Slot` of `key` referencing the inserted element or a [io.AllocationError] if it failed. diff --git a/source/coral/stack.zig b/source/coral/stack.zig deleted file mode 100755 index 6e130a6..0000000 --- a/source/coral/stack.zig +++ /dev/null @@ -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; - } - }); -} diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index 68df59f..e73919b 100755 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -1,3 +1,5 @@ +const io = @import("./io.zig"); + const math = @import("./math.zig"); pub const IntParseError = math.CheckedArithmeticError || ParseError; @@ -99,3 +101,29 @@ pub fn parse_unsigned(comptime bits: comptime_int, utf8: []const u8) IntParseErr 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]); +}