Compare commits
No commits in common. "fcd4ecd85d815f651954861ea59421392f9ed6ca" and "d49608f7bb9a6660a24890f99958e578f71da379" have entirely different histories.
fcd4ecd85d
...
d49608f7bb
128
src/core/io.zig
128
src/core/io.zig
|
@ -3,53 +3,73 @@ const meta = @import("./meta.zig");
|
||||||
const stack = @import("./stack.zig");
|
const stack = @import("./stack.zig");
|
||||||
const testing = @import("./testing.zig");
|
const testing = @import("./testing.zig");
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
pub const Allocation = struct {
|
|
||||||
existing: ?[*]u8,
|
|
||||||
size: usize
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Dynamic memory allocation interface.
|
/// Dynamic memory allocation interface.
|
||||||
///
|
///
|
||||||
pub const Allocator = meta.Function(Allocation, ?[*]u8);
|
pub const Allocator = union (enum) {
|
||||||
|
bound: struct {
|
||||||
|
alloc: fn (usize) ?[*]u8,
|
||||||
|
dealloc: fn ([*]u8) void,
|
||||||
|
},
|
||||||
|
|
||||||
///
|
arena: struct {
|
||||||
///
|
buffer: []u8,
|
||||||
///
|
cursor: usize = 0,
|
||||||
pub const ArenaAllocator = struct {
|
},
|
||||||
region: []u8,
|
|
||||||
cursor: usize = 0,
|
|
||||||
|
|
||||||
///
|
///
|
||||||
|
/// [MakeError.OutOfMemory] is used to indicate there is not enough memory available for a given
|
||||||
|
/// operation.
|
||||||
///
|
///
|
||||||
///
|
pub const MakeError = error {
|
||||||
pub fn allocator(arena_allocator: *ArenaAllocator) Allocator {
|
OutOfMemory,
|
||||||
return Allocator.fromClosure(arena_allocator, struct {
|
};
|
||||||
fn call(context: *ArenaAllocator, allocation: Allocation) ?[*]u8 {
|
|
||||||
_ = allocation;
|
|
||||||
_ = context;
|
|
||||||
|
|
||||||
return null;
|
///
|
||||||
}
|
/// Frees `allocation` using `allocator`.
|
||||||
}.call);
|
///
|
||||||
|
pub fn free(allocator: *Allocator, allocation: anytype) void {
|
||||||
|
switch (@typeInfo(@TypeOf(allocation))) {
|
||||||
|
.Pointer => |pointer| if (pointer.size == .Slice)
|
||||||
|
@compileError("`allocation` cannot be a slice"),
|
||||||
|
|
||||||
|
else => @compileError("`allocation` must be a pointer"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@typeInfo(@TypeOf(allocation)) != .Pointer)
|
||||||
|
@compileError("`allocation` must be a pointer");
|
||||||
|
|
||||||
|
// TODO: Implement arena de-allocation.
|
||||||
|
switch (allocator.*) {
|
||||||
|
.bound => |bound| bound.dealloc(@ptrCast([*]u8, allocation)),
|
||||||
|
.arena => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
/// Attempts to allocate `size` number of `Element`s using `allocator`.
|
||||||
///
|
///
|
||||||
pub const MakeError = error {
|
/// Returns the allocation or a [MakeError] if it failed.
|
||||||
OutOfMemory,
|
///
|
||||||
|
pub fn make(allocator: *Allocator, comptime Element: type, size: usize) MakeError![*]Element {
|
||||||
|
switch (allocator.*) {
|
||||||
|
.bound => |bound| return @ptrCast([*]Element, @alignCast(@alignOf(Element),
|
||||||
|
bound.alloc(@sizeOf(Element) * size) orelse return error.OutOfMemory)),
|
||||||
|
|
||||||
|
.arena => |*stack| {
|
||||||
|
defer stack.cursor += size;
|
||||||
|
|
||||||
|
return @ptrCast([*]Element, @alignCast(@alignOf(Element), stack.buffer.ptr));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Closure that captures a reference to readable resources like block devices, memory buffers,
|
/// Closure that captures a reference to readable resources like block devices, memory buffers,
|
||||||
/// network sockets, and more.
|
/// network sockets, and more.
|
||||||
///
|
///
|
||||||
pub const Reader = meta.Function([]u8, usize);
|
pub const Reader = meta.Function(@sizeOf(usize), []u8, usize);
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Returns a state machine for lazily computing all `Element` components of a given source input
|
/// Returns a state machine for lazily computing all `Element` components of a given source input
|
||||||
|
@ -167,7 +187,7 @@ test "Spliterator of string literals" {
|
||||||
/// Closure that captures a reference to writable resources like block devices, memory buffers,
|
/// Closure that captures a reference to writable resources like block devices, memory buffers,
|
||||||
/// network sockets, and more.
|
/// network sockets, and more.
|
||||||
///
|
///
|
||||||
pub const Writer = meta.Function([]const u8, usize);
|
pub const Writer = meta.Function(@sizeOf(usize), []const u8, usize);
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Returns a sliced reference of the raw bytes in `pointer`.
|
/// Returns a sliced reference of the raw bytes in `pointer`.
|
||||||
|
@ -317,24 +337,6 @@ test "Find first of sequence" {
|
||||||
try testing.expect(findFirstOf([]const u8, haystack, &.{"baz", "bar"}, testEquality) == null);
|
try testing.expect(findFirstOf([]const u8, haystack, &.{"baz", "bar"}, testEquality) == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
pub fn free(allocator: Allocator, allocated_memory: anytype) void {
|
|
||||||
if (allocator.call(.{
|
|
||||||
.existing = @ptrCast([*]u8, switch (@typeInfo(@TypeOf(allocated_memory))) {
|
|
||||||
.Pointer => |info| switch (info.size) {
|
|
||||||
.One, .Many, .C => allocated_memory,
|
|
||||||
.Slice => allocated_memory.ptr,
|
|
||||||
},
|
|
||||||
|
|
||||||
else => @compileError("`allocated_memory` must be a pointer"),
|
|
||||||
}),
|
|
||||||
|
|
||||||
.size = 0,
|
|
||||||
}) != null) unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Returns a deterministic hash code compiled from each byte in `bytes`.
|
/// Returns a deterministic hash code compiled from each byte in `bytes`.
|
||||||
///
|
///
|
||||||
|
@ -355,16 +357,6 @@ test "Hashing bytes" {
|
||||||
try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42}));
|
try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42}));
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
pub fn makeMany(comptime Element: type, allocator: Allocator, size: usize) MakeError![*]Element {
|
|
||||||
return @ptrCast([*]Element, @alignCast(@alignOf(Element), allocator.call(.{
|
|
||||||
.existing = null,
|
|
||||||
.size = size,
|
|
||||||
}) orelse return error.OutOfMemory));
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Swaps the `Data` in `this` with `that`.
|
/// Swaps the `Data` in `this` with `that`.
|
||||||
///
|
///
|
||||||
|
@ -385,19 +377,21 @@ test "Data swapping" {
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// [Writer] that silently consumes all given data without failure and throws it away.
|
/// Returns a [Writer] that silently consumes all given data without failure and throws it away.
|
||||||
///
|
///
|
||||||
/// This is commonly used for testing or redirected otherwise unwanted output data that has to be
|
/// This is commonly used for testing or redirected otherwise unwanted output data that has to be
|
||||||
/// sent somewhere for whatever reason.
|
/// sent somewhere for whatever reason.
|
||||||
///
|
///
|
||||||
pub const null_writer = Writer.from(struct {
|
pub fn nullWriter() Writer {
|
||||||
fn write(buffer: []const u8) usize {
|
return Writer.capture(@as(usize, 0), struct {
|
||||||
return buffer.len;
|
fn write(_: usize, buffer: []const u8) usize {
|
||||||
}
|
return buffer.len;
|
||||||
}.write);
|
}
|
||||||
|
}.write);
|
||||||
|
}
|
||||||
|
|
||||||
test "Null writing" {
|
test "Null writing" {
|
||||||
const sequence = "foo";
|
const sequence = "foo";
|
||||||
|
|
||||||
try testing.expect(null_writer.call(sequence) == sequence.len);
|
try testing.expect(nullWriter().call(sequence) == sequence.len);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,10 @@ pub fn FnReturn(comptime Fn: type) type {
|
||||||
/// Returns a single-input single-output closure type where `In` represents the input type, `Out`
|
/// Returns a single-input single-output closure type where `In` represents the input type, `Out`
|
||||||
/// represents the output type, and `captures_size` represents the size of the closure context.
|
/// represents the output type, and `captures_size` represents the size of the closure context.
|
||||||
///
|
///
|
||||||
pub fn Function(comptime In: type, comptime Out: type) type {
|
pub fn Function(comptime captures_size: usize, comptime In: type, comptime Out: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
callErased: fn (*anyopaque, In) Out,
|
callErased: fn (*anyopaque, In) Out,
|
||||||
context: *anyopaque,
|
context: [captures_size]u8,
|
||||||
|
|
||||||
fn Invoker(comptime Context: type) type {
|
|
||||||
return if (Context == void) fn (In) Out else fn (Context, In) Out;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Function type.
|
/// Function type.
|
||||||
|
@ -30,56 +26,38 @@ pub fn Function(comptime In: type, comptime Out: type) type {
|
||||||
///
|
///
|
||||||
/// Invokes `self` with `input`, producing a result according to the current context data.
|
/// Invokes `self` with `input`, producing a result according to the current context data.
|
||||||
///
|
///
|
||||||
pub fn call(self: Self, input: In) Out {
|
pub fn call(self: *Self, input: In) Out {
|
||||||
return self.callErased(self.context, input);
|
return self.callErased(&self.context, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Creates and returns a [Self] using the `invoke` as the behavior executed when [call] or
|
/// Creates a new [Self] by capturing the `captures` value as the context and `invoke` as
|
||||||
/// [callErased] is called.
|
/// the as the behavior executed when [call] or [callErased] is called.
|
||||||
///
|
|
||||||
/// For creating a closure-style function, see [fromClosure].
|
|
||||||
///
|
|
||||||
pub fn from(comptime invoke: fn (In) Out) Self {
|
|
||||||
return .{
|
|
||||||
.context = undefined,
|
|
||||||
|
|
||||||
.callErased = struct {
|
|
||||||
fn callErased(_: *anyopaque, input: In) Out {
|
|
||||||
return invoke(input);
|
|
||||||
}
|
|
||||||
}.callErased,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Creates and returns a [Self] by capturing the `context` value as the capture context and
|
|
||||||
/// `invoke` as the behavior executed when [call] or [callErased] is called.
|
|
||||||
///
|
///
|
||||||
/// The newly created [Self] is returned.
|
/// The newly created [Self] is returned.
|
||||||
///
|
///
|
||||||
pub fn fromClosure(context: anytype, comptime invoke: fn (@TypeOf(context), In) Out) Self {
|
pub fn capture(captures: anytype, comptime invoke: fn (@TypeOf(captures), In) Out) Self {
|
||||||
const Context = @TypeOf(context);
|
const Captures = @TypeOf(captures);
|
||||||
|
|
||||||
switch (@typeInfo(Context)) {
|
if (@sizeOf(Captures) > captures_size)
|
||||||
.Pointer => |info| if (info.size == .Slice)
|
@compileError("`captures` exceeds the size limit of the capture context");
|
||||||
@compileError("`context` cannot be a slice"),
|
|
||||||
|
|
||||||
.Void => {},
|
const captures_align = @alignOf(Captures);
|
||||||
else => @compileError("`context` must be a pointer"),
|
|
||||||
}
|
|
||||||
|
|
||||||
var function = Self{
|
var function = Self{
|
||||||
.context = @ptrCast(*anyopaque, context),
|
.context = undefined,
|
||||||
|
|
||||||
.callErased = struct {
|
.callErased = struct {
|
||||||
fn callErased(erased: *anyopaque, input: In) Out {
|
fn callErased(erased: *anyopaque, input: In) Out {
|
||||||
return if (Context == void) invoke(input) else invoke(@ptrCast(
|
return invoke(if (Captures == void) {} else @ptrCast(*Captures,
|
||||||
*Context, @alignCast(@alignOf(Context), erased)).*, input);
|
@alignCast(@alignOf(Captures), erased)).*, input);
|
||||||
}
|
}
|
||||||
}.callErased,
|
}.callErased,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Captures != void)
|
||||||
|
@ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures;
|
||||||
|
|
||||||
return function;
|
return function;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -113,7 +113,7 @@ test "Fixed stack of string literals" {
|
||||||
///
|
///
|
||||||
/// Potential errors that may occur while trying to push one or more elements into a stack.
|
/// Potential errors that may occur while trying to push one or more elements into a stack.
|
||||||
///
|
///
|
||||||
pub const PushError = io.MakeError;
|
pub const PushError = io.Allocator.MakeError;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Returns an [io.Writer] wrapping `fixed_stack`.
|
/// Returns an [io.Writer] wrapping `fixed_stack`.
|
||||||
|
@ -122,7 +122,7 @@ pub const PushError = io.MakeError;
|
||||||
/// referenced by `fixed_stack` until it is full.
|
/// referenced by `fixed_stack` until it is full.
|
||||||
///
|
///
|
||||||
pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer {
|
pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer {
|
||||||
return io.Writer.fromClosure(fixed_stack, struct {
|
return io.Writer.capture(fixed_stack, struct {
|
||||||
fn write(stack: *Fixed(u8), buffer: []const u8) usize {
|
fn write(stack: *Fixed(u8), buffer: []const u8) usize {
|
||||||
stack.pushAll(buffer) catch |err| switch (err) {
|
stack.pushAll(buffer) catch |err| switch (err) {
|
||||||
error.OutOfMemory => return 0,
|
error.OutOfMemory => return 0,
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
const Allocator = io.Allocator;
|
const Allocator = io.Allocator;
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
allocator: Allocator,
|
allocator: *Allocator,
|
||||||
load_limit: f32,
|
load_limit: f32,
|
||||||
buckets: []Bucket,
|
buckets: []Bucket,
|
||||||
filled: usize,
|
filled: usize,
|
||||||
|
@ -37,7 +37,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
/// Deinitializes `self`, preventing any further use.
|
/// Deinitializes `self`, preventing any further use.
|
||||||
///
|
///
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
io.free(self.allocator, self.buckets);
|
self.allocator.free(self.buckets.ptr);
|
||||||
|
|
||||||
self.buckets = &.{};
|
self.buckets = &.{};
|
||||||
}
|
}
|
||||||
|
@ -45,13 +45,13 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
///
|
///
|
||||||
/// Initializes a [Self] using `allocator` as the memory allocation strategy.
|
/// Initializes a [Self] using `allocator` as the memory allocation strategy.
|
||||||
///
|
///
|
||||||
/// Returns a new [Self] value or an [io.MakeError] if initializing failed.
|
/// Returns a new [Self] value or an [io.Allocator.MakeError] if initializing failed.
|
||||||
///
|
///
|
||||||
pub fn init(allocator: Allocator) io.MakeError!Self {
|
pub fn init(allocator: *Allocator) Allocator.MakeError!Self {
|
||||||
const initial_capacity = 4;
|
const initial_capacity = 4;
|
||||||
|
|
||||||
return Self{
|
return Self{
|
||||||
.buckets = (try io.makeMany(Bucket, allocator, initial_capacity))[0 .. initial_capacity],
|
.buckets = (try allocator.make(Bucket, initial_capacity))[0 .. initial_capacity],
|
||||||
.filled = 0,
|
.filled = 0,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.load_limit = 0.75,
|
.load_limit = 0.75,
|
||||||
|
@ -99,12 +99,11 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
if (self.loadFactor() >= self.load_limit) {
|
if (self.loadFactor() >= self.load_limit) {
|
||||||
const old_buckets = self.buckets;
|
const old_buckets = self.buckets;
|
||||||
|
|
||||||
defer io.free(self.allocator, old_buckets);
|
defer self.allocator.free(old_buckets.ptr);
|
||||||
|
|
||||||
const bucket_count = old_buckets.len * 2;
|
const bucket_count = old_buckets.len * 2;
|
||||||
|
|
||||||
self.buckets = (try io.makeMany(Bucket, self.allocator,
|
self.buckets = (try self.allocator.make(Bucket, bucket_count))[0 .. bucket_count];
|
||||||
bucket_count))[0 .. bucket_count];
|
|
||||||
|
|
||||||
for (old_buckets) |bucket, index| self.buckets[index] = bucket;
|
for (old_buckets) |bucket, index| self.buckets[index] = bucket;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +163,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type,
|
||||||
/// [InsertError.KeyExists] occurs when an insertion was attempted on a table with a matching key
|
/// [InsertError.KeyExists] occurs when an insertion was attempted on a table with a matching key
|
||||||
/// already present.
|
/// already present.
|
||||||
///
|
///
|
||||||
pub const InsertError = io.MakeError || error {
|
pub const InsertError = io.Allocator.MakeError || error {
|
||||||
KeyExists,
|
KeyExists,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -198,10 +197,8 @@ pub const string_literal_context = KeyContext([]const u8){
|
||||||
|
|
||||||
test "Hash table manipulation with string literal context" {
|
test "Hash table manipulation with string literal context" {
|
||||||
var buffer = [_]u8{0} ** 1024;
|
var buffer = [_]u8{0} ** 1024;
|
||||||
var arena_allocator = io.ArenaAllocator{.region = &buffer};
|
var allocator = io.Allocator{.arena = .{.buffer = &buffer}};
|
||||||
|
var table = try Hashed([]const u8, u32, string_literal_context).init(&allocator);
|
||||||
var table =
|
|
||||||
try Hashed([]const u8, u32, string_literal_context).init(arena_allocator.allocator());
|
|
||||||
|
|
||||||
defer table.deinit();
|
defer table.deinit();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue