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; } }