const builtin = @import("builtin"); const coral = @import("./coral.zig"); const formatting = @import("./bytes/formatting.zig"); const std = @import("std"); pub const PrintError = error{BufferOverflow}; pub const ReadOnlySpan = struct { bytes: []const u8, read_cursor: usize = 0, pub fn read(self: *ReadOnlySpan, buffer: []u8) usize { _ = self; _ = buffer; // TODO: Implement. unreachable; } }; pub const ReadWriteError = error{IncompleteWrite}; pub const Readable = coral.Callable(usize, &.{[]u8}); pub const ReadWriteSpan = struct { bytes: []u8, write_cursor: usize = 0, read_cursor: usize = 0, pub fn read(self: *ReadWriteSpan, buffer: []u8) usize { _ = self; _ = buffer; // TODO: Implement. unreachable; } pub fn put(self: *ReadWriteSpan, byte: u8) bool { if (self.write_cursor >= self.bytes.len) { return false; } self.bytes[self.write_cursor] = byte; self.write_cursor += 1; return true; } pub fn write(self: *ReadWriteSpan, buffer: []const u8) usize { const written = @min(buffer.len, self.bytes.len - self.write_cursor); @memcpy(self.bytes[self.write_cursor .. self.write_cursor + written], buffer[0..written]); self.write_cursor += written; return written; } pub fn writer(self: *ReadWriteSpan) Writable { return .initRef(self, write); } }; pub fn Span(comptime Ptr: type) type { return switch (@typeInfo(Ptr)) { .pointer => |pointer| if (pointer.is_const) ReadOnlySpan else ReadWriteSpan, else => @compileError("param `Ptr` must be a non-const pointer type, not " ++ @typeName(Ptr)), }; } pub const WriteCount = struct { written: usize = 0, fn write(self: *WriteCount, input: []const u8) usize { self.written += input.len; return input.len; } fn writable(self: *WriteCount) Writable { return .initRef(self, WriteCount.write); } }; pub const Writable = coral.Callable(usize, &.{[]const u8}); pub const null_writer = Writable.initFn(writeNull); pub fn allocFormatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![:0]u8 { const formatted_len = countFormatted(format, args); const buffer = try allocator.allocSentinel(u8, formatted_len, 0); errdefer { allocator.free(buffer); } // TODO: This is messy. return @constCast(printFormatted(buffer, format, args) catch unreachable); } pub fn altFormat(value: anytype, comptime format: fn (@TypeOf(value), Writable) ReadWriteError!void) struct { formattable: @TypeOf(value), pub fn writeFormat(self: @This(), output: Writable) ReadWriteError!void { return format(self.formattable, output); } } { return .{ .formattable = value, }; } pub fn countFormatted(comptime format: []const u8, args: anytype) usize { var count = WriteCount{}; writeFormatted(count.writable(), format, args) catch unreachable; return count.written; } pub fn printFormatted(buffer: [:0]u8, comptime format: []const u8, args: anytype) PrintError![:0]const u8 { const len = countFormatted(format, args); if (len > buffer.len) { return error.BufferOverflow; } var buffer_span = span(buffer); writeFormatted(buffer_span.writer(), format, args) catch unreachable; if (buffer.len < len) { buffer[len] = 0; } return buffer[0..len :0]; } pub fn span(ptr: anytype) Span(@TypeOf(ptr)) { return .{ .bytes = switch (@typeInfo(@TypeOf(ptr)).pointer.size) { .slice => std.mem.sliceAsBytes(ptr), .one => std.mem.asBytes(ptr), else => @compileError("Parameter `memory` must be a slice or single-element pointer"), }, }; } pub fn stackWriter(stack: *coral.Stack(u8)) Writable { const writing = struct { fn write(self: *coral.Stack(u8), buffer: []const u8) usize { self.grow(buffer.len) catch { return 0; }; std.debug.assert(self.pushAll(buffer)); return buffer.len; } }; return .initRef(stack, writing.write); } pub fn streamAll(input: Readable, output: Writable) ReadWriteError!usize { var buffer: [512]u8 = undefined; var copied: usize = 0; while (true) { const read = input.call(.{&buffer}); if (read == 0) { return copied; } if (output.call(.{buffer[0..read]}) != read) { return error.IncompleteWrite; } copied += read; } } pub fn streamN(input: Readable, output: Writable, limit: usize) ReadWriteError!usize { var buffer: [512]u8 = undefined; var remaining = limit; while (true) { const read = input.call(.{buffer[0..@min(remaining, buffer.len)]}); if (read == 0) { return limit - remaining; } if (output.yield(.{buffer[0..read]}) != read) { return error.IncompleteWrite; } remaining -= read; } } pub fn writeAll(output: Writable, data: []const u8) ReadWriteError!void { if (output.call(.{data}) != data.len) { return error.IncompleteWrite; } } pub fn writeN(output: Writable, data: []const u8, count: usize) ReadWriteError!void { var remaining = count; while (remaining != 0) : (remaining -= 1) { try writeAll(output, data); } } pub fn writeFormatable(output: Writable, formatable: anytype) ReadWriteError!void { const Formatable = @TypeOf(formatable); switch (@typeInfo(Formatable)) { .pointer => |pointer| { const error_message = std.fmt.comptimePrint("{s} is not a string-like type", .{@typeName(Formatable)}); switch (pointer.size) { .one => switch (@typeInfo(pointer.child)) { .array => |array| switch (array.child == u8) { true => try coral.bytes.writeAll(output, formatable.*[0..]), false => @compileError(error_message), }, .@"struct", .@"union", .@"enum" => { try formatable.writeFormat(output); }, else => @compileError(error_message), }, .slice => switch (pointer.child == u8) { true => try coral.bytes.writeAll(output, formatable), false => @compileError(error_message), }, .many => switch ((pointer.sentinel() != null) and (pointer.child == u8)) { true => try coral.bytes.writeAll(output, std.mem.span(formatable)), false => @compileError(error_message), }, else => false, } }, .@"struct", .@"union", .@"enum" => { try formatable.writeFormat(output); }, else => { @compileError(std.fmt.comptimePrint("`{s}` is not a valid placeholder type", .{ @typeName(Formatable), })); }, } } pub fn writeFormatted(output: Writable, comptime format: []const u8, args: anytype) ReadWriteError!void { comptime { if (!std.unicode.utf8ValidateSlice(format)) { @compileError("`format` must be a valid UTF8 sequence"); } } const Args = @TypeOf(args); const args_struct = switch (@typeInfo(Args)) { .@"struct" => |@"struct"| @"struct", else => @compileError(std.fmt.comptimePrint("`args` must be a struct type, not {s}", .{@typeName(Args)})), }; comptime var tokens = formatting.TokenStream.init(format); inline while (comptime tokens.next()) |token| { switch (token) { .invalid => |invalid| { @compileError(std.fmt.comptimePrint("unexpected `{s}` in format sequence `{s}`", .{ invalid, format })); }, .literal => |literal| { try coral.bytes.writeAll(output, literal); }, .escaped => |escaped| { try coral.bytes.writeAll(output, std.mem.asBytes(&escaped)); }, .placeholder => |placeholder| { if (args_struct.is_tuple) { const index = comptime coral.utf8.DecFormat.c.parse(usize, placeholder) orelse { @compileError(std.fmt.comptimePrint("{s} in format string `{s} is not a valid tuple index", .{ placeholder, format, })); }; if (index >= args.len) { @compileError(std.fmt.comptimePrint("format string `{s}` uses index `{s}` not present in {s}", .{ format, placeholder, @typeName(Args), })); } try writeFormatable(output, args[index]); } else { if (!@hasField(Args, placeholder)) { @compileError(std.fmt.comptimePrint("format string `{s}` uses field `{s}` not present in {s}", .{ format, placeholder, @typeName(Args), })); } try writeFormatable(output, @field(args, placeholder)); } }, } } } pub fn writeLittle(output: Writable, value: anytype) ReadWriteError!void { switch (builtin.cpu.arch.endian()) { .little => { try writeAll(output, std.mem.asBytes(&value)); }, .big => { const Value = @TypeOf(value); switch (@typeInfo(Value)) { .@"struct", .array => { var copy = value; std.mem.byteSwapAllFields(Value, ©); try writeAll(output, std.mem.asBytes(©)); }, .int, .float, .bool => { try writeAll(output, std.mem.asBytes(&@byteSwap(value))); }, .@"enum" => { try writeLittle(output, @intFromEnum(value)); }, else => { @compileError(std.fmt.comptimePrint("{s} is not byte-swappable", .{@typeName(Value)})); }, } }, } } fn writeNull(buffer: []const u8) usize { return buffer.len; } pub fn writeSentineled(output: Writable, data: []const u8, sentinel: u8) coral.bytes.ReadWriteError!void { try writeAll(output, data); try writeAll(output, (&sentinel)[0..1]); }