2025-05-12 00:21:52 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-06-11 09:07:14 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-05-12 00:21:52 +01:00
|
|
|
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)),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-06-11 09:07:14 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-05-12 00:21:52 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2025-07-31 20:37:27 +01:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-05-12 00:21:52 +01:00
|
|
|
pub fn countFormatted(comptime format: []const u8, args: anytype) usize {
|
2025-06-11 09:07:14 +01:00
|
|
|
var count = WriteCount{};
|
2025-05-12 00:21:52 +01:00
|
|
|
|
2025-06-11 09:07:14 +01:00
|
|
|
writeFormatted(count.writable(), format, args) catch unreachable;
|
2025-05-12 00:21:52 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-11 09:07:14 +01:00
|
|
|
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),
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-12 00:21:52 +01:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-11 09:07:14 +01:00
|
|
|
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)})),
|
|
|
|
};
|
|
|
|
|
2025-05-12 00:21:52 +01:00
|
|
|
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| {
|
2025-06-11 09:07:14 +01:00
|
|
|
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));
|
2025-05-12 00:21:52 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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, ©);
|
|
|
|
|
2025-06-11 09:07:14 +01:00
|
|
|
try writeAll(output, std.mem.asBytes(©));
|
2025-05-12 00:21:52 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
.int, .float, .bool => {
|
2025-06-11 09:07:14 +01:00
|
|
|
try writeAll(output, std.mem.asBytes(&@byteSwap(value)));
|
|
|
|
},
|
|
|
|
|
|
|
|
.@"enum" => {
|
|
|
|
try writeLittle(output, @intFromEnum(value));
|
2025-05-12 00:21:52 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
else => {
|
|
|
|
@compileError(std.fmt.comptimePrint("{s} is not byte-swappable", .{@typeName(Value)}));
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn writeNull(buffer: []const u8) usize {
|
|
|
|
return buffer.len;
|
|
|
|
}
|
|
|
|
|
2025-06-11 09:07:14 +01:00
|
|
|
pub fn writeSentineled(output: Writable, data: []const u8, sentinel: u8) coral.bytes.ReadWriteError!void {
|
2025-05-12 00:21:52 +01:00
|
|
|
try writeAll(output, data);
|
2025-06-11 09:07:14 +01:00
|
|
|
try writeAll(output, (&sentinel)[0..1]);
|
2025-05-12 00:21:52 +01:00
|
|
|
}
|