ona/src/coral/bytes.zig

373 lines
11 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 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, &copy);
try writeAll(output, std.mem.asBytes(&copy));
},
.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]);
}