278 lines
6.9 KiB
Zig
278 lines
6.9 KiB
Zig
|
const builtin = @import("builtin");
|
||
|
|
||
|
const coral = @import("./coral.zig");
|
||
|
|
||
|
const slices = @import("./slices.zig");
|
||
|
|
||
|
const std = @import("std");
|
||
|
|
||
|
pub const Byte = u8;
|
||
|
|
||
|
pub const Decoder = coral.io.Functor(coral.io.Error!void, &.{[]coral.Byte});
|
||
|
|
||
|
pub const Error = error {
|
||
|
UnavailableResource,
|
||
|
};
|
||
|
|
||
|
pub fn Functor(comptime Output: type, comptime input_types: []const type) type {
|
||
|
const InputTuple = std.meta.Tuple(input_types);
|
||
|
|
||
|
return struct {
|
||
|
context: *const anyopaque,
|
||
|
apply_with_context: *const fn (*const anyopaque, InputTuple) Output,
|
||
|
|
||
|
const Self = @This();
|
||
|
|
||
|
pub fn apply(self: *const Self, inputs: InputTuple) Output {
|
||
|
return self.apply_with_context(self.context, inputs);
|
||
|
}
|
||
|
|
||
|
pub fn bind(comptime State: type, state: *const State, comptime invoke: anytype) Self {
|
||
|
const is_zero_aligned = @alignOf(State) == 0;
|
||
|
|
||
|
return .{
|
||
|
.context = if (is_zero_aligned) state else @ptrCast(state),
|
||
|
|
||
|
.apply_with_context = struct {
|
||
|
fn invoke_concrete(context: *const anyopaque, inputs: InputTuple) Output {
|
||
|
if (is_zero_aligned) {
|
||
|
return @call(.auto, invoke, .{@as(*const State, @ptrCast(context))} ++ inputs);
|
||
|
}
|
||
|
|
||
|
return switch (@typeInfo(@typeInfo(@TypeOf(invoke)).Fn.return_type.?)) {
|
||
|
.ErrorUnion => try @call(.auto, invoke, .{@as(*const State, @ptrCast(@alignCast(context)))} ++ inputs),
|
||
|
else => @call(.auto, invoke, .{@as(*const State, @ptrCast(@alignCast(context)))} ++ inputs),
|
||
|
};
|
||
|
}
|
||
|
}.invoke_concrete,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn bind_fn(comptime invoke: anytype) Self {
|
||
|
return .{
|
||
|
.context = undefined,
|
||
|
|
||
|
.apply_with_context = struct {
|
||
|
fn invoke_concrete(_: *const anyopaque, inputs: InputTuple) Output {
|
||
|
return @call(.auto, invoke, inputs);
|
||
|
}
|
||
|
}.invoke_concrete,
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn Generator(comptime Output: type, comptime input_types: []const type) type {
|
||
|
const InputTuple = std.meta.Tuple(input_types);
|
||
|
|
||
|
return struct {
|
||
|
context: *anyopaque,
|
||
|
yield_with_context: *const fn (*anyopaque, InputTuple) Output,
|
||
|
|
||
|
const Self = @This();
|
||
|
|
||
|
pub fn bind(comptime State: type, state: *State, comptime invoke: anytype) Self {
|
||
|
const is_zero_aligned = @alignOf(State) == 0;
|
||
|
|
||
|
return .{
|
||
|
.context = if (is_zero_aligned) state else @ptrCast(state),
|
||
|
|
||
|
.yield_with_context = struct {
|
||
|
fn invoke_concrete(context: *anyopaque, inputs: InputTuple) Output {
|
||
|
if (is_zero_aligned) {
|
||
|
return @call(.auto, invoke, .{@as(*State, @ptrCast(context))} ++ inputs);
|
||
|
}
|
||
|
|
||
|
return switch (@typeInfo(@typeInfo(@TypeOf(invoke)).Fn.return_type.?)) {
|
||
|
.ErrorUnion => try @call(.auto, invoke, .{@as(*State, @ptrCast(@alignCast(context)))} ++ inputs),
|
||
|
else => @call(.auto, invoke, .{@as(*State, @ptrCast(@alignCast(context)))} ++ inputs),
|
||
|
};
|
||
|
}
|
||
|
}.invoke_concrete,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn bind_fn(comptime invoke: anytype) Self {
|
||
|
return .{
|
||
|
.context = undefined,
|
||
|
|
||
|
.yield_with_context = struct {
|
||
|
fn invoke_concrete(_: *const anyopaque, inputs: InputTuple) Output {
|
||
|
return @call(.auto, invoke, inputs);
|
||
|
}
|
||
|
}.invoke_concrete,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn yield(self: *const Self, inputs: InputTuple) Output {
|
||
|
return self.yield_with_context(self.context, inputs);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub const NullWritable = struct {
|
||
|
written: usize = 0,
|
||
|
|
||
|
pub fn writer(self: *NullWritable) Writer {
|
||
|
return Writer.bind(NullWritable, self, write);
|
||
|
}
|
||
|
|
||
|
pub fn write(self: *NullWritable, buffer: []const u8) !usize {
|
||
|
self.written += buffer.len;
|
||
|
|
||
|
return buffer.len;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
pub const PrintError = Error || error {
|
||
|
IncompleteWrite,
|
||
|
};
|
||
|
|
||
|
pub const Reader = Generator(Error!usize, &.{[]coral.Byte});
|
||
|
|
||
|
pub const Writer = Generator(Error!usize, &.{[]const coral.Byte});
|
||
|
|
||
|
const native_endian = builtin.cpu.arch.endian();
|
||
|
|
||
|
pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte {
|
||
|
const buffer = coral.Stack(coral.Byte){.allocator = allocator};
|
||
|
|
||
|
errdefer buffer.deinit();
|
||
|
|
||
|
const streamed = try stream_all(input.reader(), buffer.writer());
|
||
|
|
||
|
return buffer.to_allocation(streamed);
|
||
|
}
|
||
|
|
||
|
pub fn are_equal(a: []const Byte, b: []const Byte) bool {
|
||
|
if (a.len != b.len) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for (0 .. a.len) |i| {
|
||
|
if (a[i] != b[i]) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
pub const bits_per_byte = 8;
|
||
|
|
||
|
pub fn bytes_of(value: anytype) []const Byte {
|
||
|
const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
|
||
|
|
||
|
return switch (pointer_info.size) {
|
||
|
.One => @as([*]const Byte, @ptrCast(value))[0 .. @sizeOf(pointer_info.child)],
|
||
|
.Slice => @as([*]const Byte, @ptrCast(value.ptr))[0 .. @sizeOf(pointer_info.child) * value.len],
|
||
|
else => @compileError("`value` must be single-element pointer or slice type"),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn ends_with(haystack: []const Byte, needle: []const Byte) bool {
|
||
|
if (needle.len > haystack.len) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return are_equal(haystack[haystack.len - needle.len ..], needle);
|
||
|
}
|
||
|
|
||
|
pub fn print(writer: Writer, utf8: []const u8) PrintError!void {
|
||
|
if (try writer.yield(.{utf8}) != utf8.len) {
|
||
|
return error.IncompleteWrite;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn skip_n(input: Reader, distance: u64) Error!void {
|
||
|
var buffer = @as([512]coral.Byte, undefined);
|
||
|
var remaining = distance;
|
||
|
|
||
|
while (remaining != 0) {
|
||
|
const read = try input.yield(.{buffer[0 .. @min(remaining, buffer.len)]});
|
||
|
|
||
|
if (read == 0) {
|
||
|
return error.UnavailableResource;
|
||
|
}
|
||
|
|
||
|
remaining -= read;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn read_foreign(input: Reader, comptime Type: type) Error!Type {
|
||
|
const decoded = try read_native(input, Type);
|
||
|
|
||
|
return switch (@typeInfo(input)) {
|
||
|
.Struct => std.mem.byteSwapAllFields(Type, &decoded),
|
||
|
else => @byteSwap(decoded),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn read_native(input: Reader, comptime Type: type) Error!Type {
|
||
|
var buffer = @as([@sizeOf(Type)]coral.Byte, undefined);
|
||
|
|
||
|
if (try input.yield(.{&buffer}) != buffer.len) {
|
||
|
return error.UnavailableResource;
|
||
|
}
|
||
|
|
||
|
return @as(*align(1) const Type, @ptrCast(&buffer)).*;
|
||
|
}
|
||
|
|
||
|
pub const read_little = switch (native_endian) {
|
||
|
.little => read_native,
|
||
|
.big => read_foreign,
|
||
|
};
|
||
|
|
||
|
pub const read_big = switch (native_endian) {
|
||
|
.little => read_foreign,
|
||
|
.big => read_native,
|
||
|
};
|
||
|
|
||
|
pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) {
|
||
|
var len = @as(usize, 0);
|
||
|
|
||
|
while (ptr[len] != sen) {
|
||
|
len += 1;
|
||
|
}
|
||
|
|
||
|
return ptr[0 .. len:sen];
|
||
|
}
|
||
|
|
||
|
pub fn stream_all(input: Reader, output: Writer) Error!usize {
|
||
|
var buffer = @as([512]coral.Byte, undefined);
|
||
|
var copied = @as(usize, 0);
|
||
|
|
||
|
while (true) {
|
||
|
const read = try input.apply(.{&buffer});
|
||
|
|
||
|
if (read == 0) {
|
||
|
return copied;
|
||
|
}
|
||
|
|
||
|
if (try output.apply(.{buffer[0 .. read]}) != read) {
|
||
|
return error.UnavailableResource;
|
||
|
}
|
||
|
|
||
|
copied += read;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize {
|
||
|
var buffer = @as([512]coral.Byte, undefined);
|
||
|
var remaining = limit;
|
||
|
|
||
|
while (true) {
|
||
|
const read = try input.yield(.{buffer[0 .. @min(remaining, buffer.len)]});
|
||
|
|
||
|
if (read == 0) {
|
||
|
return limit - remaining;
|
||
|
}
|
||
|
|
||
|
if (try output.yield(.{buffer[0 .. read]}) != read) {
|
||
|
return error.UnavailableResource;
|
||
|
}
|
||
|
|
||
|
remaining -= read;
|
||
|
}
|
||
|
}
|