ona/source/coral/io.zig

343 lines
9.3 KiB
Zig
Executable File

const debug = @import("./debug.zig");
const math = @import("./math.zig");
pub const AllocationError = error {
OutOfMemory,
};
pub const AllocationOptions = struct {
allocation: ?[]u8 = null,
size: usize,
};
pub const Allocator = Generator(?[]u8, AllocationOptions);
///
/// Function pointer coupled with an immutable 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: *const anyopaque,
invoker: *const fn (capture: *const anyopaque, input: Input) Output,
const Self = @This();
pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self {
return .{
.context = state,
.invoker = struct {
fn invoke_opaque(context: *const anyopaque, input: Input) Output {
const state_alignment = @alignOf(State);
if (state_alignment == 0) {
return invoker(@ptrCast(*const State, context), input);
}
return invoker(@ptrCast(*const State, @alignCast(state_alignment, context)), input);
}
}.invoke_opaque,
};
}
pub fn invoke(self: Self, input: Input) Output {
return self.invoker(self.context, input);
}
};
}
///
/// Function pointer coupled with a mutable state context for providing dynamic dispatch over a given `Input` and
/// `Output`.
///
pub fn Generator(comptime Output: type, comptime Input: type) type {
return struct {
context: *anyopaque,
invoker: *const fn (capture: *anyopaque, input: Input) Output,
const Self = @This();
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 {
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,
};
}
pub fn invoke(self: Self, input: Input) Output {
return self.invoker(self.context, input);
}
};
}
pub const Reader = Generator(?usize, []u8);
pub const StreamError = error {
ReadFailure,
WriteFailure,
};
pub fn Tag(comptime Element: type) type {
return switch (@typeInfo(Element)) {
.Enum => |info| info.tag_type,
.Union => |info| info.tag_type orelse @compileError(@typeName(Element) ++ " has no tag type"),
else => @compileError("expected enum or union type, found '" ++ @typeName(Element) ++ "'"),
};
}
pub const FixedBuffer = struct {
slice: []u8,
pub fn as_writer(self: *FixedBuffer) Writer {
return Writer.bind(FixedBuffer, self, struct {
fn write(writable_memory: *FixedBuffer, data: []const u8) ?usize {
return writable_memory.write(data);
}
}.write);
}
pub fn put(self: *FixedBuffer, 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: *FixedBuffer, 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 GrowingBuffer = struct {
allocator: Allocator,
appender: Appender,
const AppendOptions = struct {
allocator: Allocator,
bytes: []const u8,
};
const Appender = Generator(AllocationError!void, AppendOptions);
pub fn as_writer(self: *GrowingBuffer) Writer {
return Writer.bind(GrowingBuffer, self, struct {
fn write(growing_buffer: *GrowingBuffer, bytes: []const u8) ?usize {
growing_buffer.write(bytes) catch return null;
return bytes.len;
}
}.write);
}
pub fn bind(comptime State: type, allocator: Allocator, state: *State, comptime appender: fn (capture: *State, allocator: Allocator, bytes: []const u8) AllocationError!void) GrowingBuffer {
return .{
.appender = Appender.bind(State, state, struct {
fn append(self: *State, options: AppendOptions) AllocationError!void {
return appender(self, options.allocator, options.bytes);
}
}.append),
.allocator = allocator,
};
}
pub fn write(self: GrowingBuffer, bytes: []const u8) AllocationError!void {
return self.appender.invoke(.{
.allocator = self.allocator,
.bytes = bytes,
});
}
};
pub const Writer = Generator(?usize, []const u8);
pub fn allocate_many(comptime Type: type, amount: usize, allocator: Allocator) AllocationError![]Type {
if (@sizeOf(Type) == 0) {
@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type));
}
return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type) * amount}) orelse {
return error.OutOfMemory;
}))[0 .. amount];
}
pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) {
const Type = @TypeOf(value);
if (@sizeOf(Type) == 0) {
@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type));
}
const allocation = @ptrCast(*Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type)}) orelse {
return error.OutOfMemory;
}));
allocation.* = value;
return allocation;
}
pub fn bytes_of(value: anytype) []const u8 {
const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
debug.assert(pointer_info.size == .One);
return @ptrCast([*]const u8, value)[0 .. @sizeOf(pointer_info.child)];
}
pub fn compare(this: []const u8, that: []const u8) isize {
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;
}
}
return @intCast(isize, this.len) - @intCast(isize, that.len);
}
pub fn copy(target: []u8, source: []const u8) void {
var index: usize = 0;
while (index < source.len) : (index += 1) target[index] = source[index];
}
pub fn deallocate(allocator: Allocator, allocation: anytype) void {
const Allocation = @TypeOf(allocation);
switch (@typeInfo(Allocation)) {
.Pointer => |allocation_pointer| {
_ = allocator.invoke(.{
.allocation = switch (allocation_pointer.size) {
.One => @ptrCast([*]u8, allocation)[0 .. @sizeOf(Allocation)],
.Slice => @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Allocation) * allocation.len)],
.Many, .C => @compileError("length of allocation must be known to deallocate"),
},
.size = 0,
});
},
else => @compileError("cannot deallocate " ++ allocation),
}
}
pub fn ends_with(target: []const u8, match: []const u8) bool {
if (target.len < match.len) return false;
var index = @as(usize, 0);
while (index < match.len) : (index += 1) {
if (target[target.len - (1 + index)] != match[match.len - (1 + index)]) return false;
}
return true;
}
pub fn equals(this: []const u8, that: []const u8) bool {
if (this.len != that.len) return false;
{
var index: usize = 0;
while (index < this.len) : (index += 1) if (this[index] != that[index]) return false;
}
return true;
}
var null_context = @as(usize, 0);
pub const null_allocator = Allocator.bind(&null_context, struct {
fn reallocate(context: *usize, options: AllocationOptions) ?[]u8 {
debug.assert(context.* == 0);
debug.assert(options.allocation == null);
return null;
}
});
pub const null_writer = Writer.bind(&null_context, struct {
fn write(context: *usize, buffer: []const u8) usize {
debug.assert(context.* == 0);
return buffer.len;
}
}.write);
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;
return @ptrCast([*]Element, @alignCast(@alignOf(Element), (allocator.invoke(switch (pointer_info.size) {
.Slice => .{
.allocation = @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Element) * allocation.len)],
.size = @sizeOf(Element) * amount,
},
.Many, .C, .One => @compileError("allocation must be a slice to reallocate"),
}) orelse return error.OutOfMemory).ptr))[0 .. amount];
}
pub fn sentinel_index(comptime element: type, comptime sentinel: element, sequence: [*:sentinel]const element) usize {
var index: usize = 0;
while (sequence[index] != sentinel) : (index += 1) {}
return index;
}
pub fn stream(output: Writer, input: Reader, buffer: []u8) StreamError!u64 {
var total_written: u64 = 0;
var read = input.invoke(buffer) orelse return error.ReadFailure;
while (read != 0) {
total_written += output.invoke(buffer[0..read]) orelse return error.WriteFailure;
read = input.invoke(buffer) orelse return error.ReadFailure;
}
return total_written;
}
pub fn swap(comptime Element: type, this: *Element, that: *Element) void {
const temp = this.*;
this.* = that.*;
that.* = temp;
}
pub fn tag_of(comptime value: anytype) Tag(@TypeOf(value)) {
return @as(Tag(@TypeOf(value)), value);
}
pub fn zero(target: []u8) void {
for (target) |*t| t.* = 0;
}