315 lines
8.9 KiB
Zig
315 lines
8.9 KiB
Zig
|
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 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 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 countFormatted(comptime format: []const u8, args: anytype) usize {
|
||
|
const Counter = struct {
|
||
|
written: usize,
|
||
|
|
||
|
const Self = @This();
|
||
|
|
||
|
fn write(self: *Self, input: []const u8) usize {
|
||
|
self.written += input.len;
|
||
|
|
||
|
return input.len;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var count = Counter{ .written = 0 };
|
||
|
|
||
|
writeFormatted(.initRef(&count, Counter.write), 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 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");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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| {
|
||
|
const Args = @TypeOf(args);
|
||
|
|
||
|
if (!@hasField(Args, placeholder)) {
|
||
|
@compileError(std.fmt.comptimePrint("format string `{s}` uses field `{s}` not present in {s}", .{
|
||
|
format,
|
||
|
placeholder,
|
||
|
@typeName(Args),
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
const field = @field(args, placeholder);
|
||
|
const Field = @TypeOf(field);
|
||
|
|
||
|
switch (@typeInfo(Field)) {
|
||
|
.pointer => |pointer| {
|
||
|
const error_message = std.fmt.comptimePrint("{s} is not a string-like type", .{@typeName(Field)});
|
||
|
|
||
|
switch (pointer.size) {
|
||
|
.one => switch (@typeInfo(pointer.child)) {
|
||
|
.array => |array| switch (array.child == u8) {
|
||
|
true => try coral.bytes.writeAll(output, field.*[0..]),
|
||
|
false => @compileError(error_message),
|
||
|
},
|
||
|
|
||
|
.@"struct", .@"union", .@"enum" => {
|
||
|
try field.writeFormat(output);
|
||
|
},
|
||
|
|
||
|
else => @compileError(error_message),
|
||
|
},
|
||
|
|
||
|
.slice => switch (pointer.child == u8) {
|
||
|
true => try coral.bytes.writeAll(output, field),
|
||
|
false => @compileError(error_message),
|
||
|
},
|
||
|
|
||
|
.many => switch ((pointer.sentinel() != null) and (pointer.child == u8)) {
|
||
|
true => try coral.bytes.writeAll(output, std.mem.span(field)),
|
||
|
false => @compileError(error_message),
|
||
|
},
|
||
|
|
||
|
else => false,
|
||
|
}
|
||
|
},
|
||
|
|
||
|
.@"struct", .@"union", .@"enum" => {
|
||
|
try field.writeFormat(output);
|
||
|
},
|
||
|
|
||
|
else => {
|
||
|
@compileError("Unsupported placeholder type");
|
||
|
},
|
||
|
}
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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(&value));
|
||
|
},
|
||
|
|
||
|
.int, .float, .bool => {
|
||
|
try writeAll(@byteSwap(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) void {
|
||
|
try writeAll(output, data);
|
||
|
try writeAll((&sentinel)[0..1]);
|
||
|
}
|