ona/src/coral/bytes.zig

315 lines
8.9 KiB
Zig
Raw Normal View History

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, &copy);
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]);
}