2024-05-29 19:27:02 +01:00
|
|
|
const ascii = @import("./ascii.zig");
|
|
|
|
|
|
|
|
const coral = @import("./coral.zig");
|
|
|
|
|
|
|
|
const io = @import("./io.zig");
|
|
|
|
|
|
|
|
const std = @import("std");
|
|
|
|
|
|
|
|
pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]coral.Byte {
|
|
|
|
var buffer = coral.Stack(coral.Byte){.allocator = allocator};
|
|
|
|
const formatted_len = count_formatted(format, args);
|
|
|
|
|
|
|
|
try buffer.grow(formatted_len);
|
|
|
|
|
|
|
|
errdefer buffer.deinit();
|
|
|
|
|
2024-06-20 21:54:00 +01:00
|
|
|
print_formatted(buffer.writer(), format, args) catch unreachable;
|
2024-05-29 19:27:02 +01:00
|
|
|
|
|
|
|
return buffer.to_allocation(formatted_len, 0);
|
|
|
|
}
|
|
|
|
|
2024-07-06 03:25:09 +01:00
|
|
|
pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
|
|
|
var count = io.NullWritable{};
|
2024-05-29 19:27:02 +01:00
|
|
|
|
2024-07-06 03:25:09 +01:00
|
|
|
write_formatted(count.writer(), format, args) catch unreachable;
|
2024-05-29 19:27:02 +01:00
|
|
|
|
|
|
|
return count.written;
|
|
|
|
}
|
|
|
|
|
2024-07-06 03:25:09 +01:00
|
|
|
pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]u8 {
|
|
|
|
const Seekable = struct {
|
|
|
|
buffer: []coral.io.Byte,
|
|
|
|
cursor: usize,
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize {
|
|
|
|
const range = @min(input.len, self.buffer.len - self.cursor);
|
|
|
|
const tail = self.cursor + range;
|
|
|
|
|
|
|
|
@memcpy(self.buffer[self.cursor .. tail], input);
|
|
|
|
|
|
|
|
self.cursor = tail;
|
|
|
|
|
|
|
|
return range;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const len = count_formatted(format, args);
|
|
|
|
|
|
|
|
if (len > buffer.len) {
|
|
|
|
return error.UnavailableResource;
|
|
|
|
}
|
|
|
|
|
|
|
|
var seekable = Seekable{
|
|
|
|
.buffer = buffer,
|
|
|
|
.cursor = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args);
|
|
|
|
|
|
|
|
if (buffer.len < len) {
|
|
|
|
buffer[len] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return buffer[0 .. len:0];
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void {
|
2024-05-29 19:27:02 +01:00
|
|
|
switch (@typeInfo(@TypeOf(args))) {
|
|
|
|
.Struct => |arguments_struct| {
|
|
|
|
comptime var arg_index = 0;
|
|
|
|
comptime var head = 0;
|
|
|
|
comptime var tail = 0;
|
|
|
|
|
|
|
|
inline while (tail < format.len) : (tail += 1) {
|
|
|
|
if (format[tail] == '{') {
|
|
|
|
if (tail > format.len) {
|
|
|
|
@compileError("expected an idenifier after opening `{`");
|
|
|
|
}
|
|
|
|
|
|
|
|
tail += 1;
|
|
|
|
|
|
|
|
switch (format[tail]) {
|
|
|
|
'{' => {
|
|
|
|
try io.print(writer, format[head .. (tail - 1)]);
|
|
|
|
|
|
|
|
tail += 1;
|
|
|
|
head = tail;
|
|
|
|
},
|
|
|
|
|
|
|
|
'}' => {
|
|
|
|
if (!arguments_struct.is_tuple) {
|
|
|
|
@compileError("all format specifiers must be named when using a named struct");
|
|
|
|
}
|
|
|
|
|
|
|
|
try io.print(writer, args[arg_index]);
|
|
|
|
|
|
|
|
arg_index += 1;
|
|
|
|
tail += 1;
|
|
|
|
head = tail;
|
|
|
|
},
|
|
|
|
|
|
|
|
else => {
|
|
|
|
if (arguments_struct.is_tuple) {
|
|
|
|
@compileError("format specifiers cannot be named when using a tuple struct");
|
|
|
|
}
|
|
|
|
|
2024-07-06 03:25:09 +01:00
|
|
|
try io.write_all(writer, format[head .. (tail - 1)]);
|
2024-05-29 19:27:02 +01:00
|
|
|
|
|
|
|
head = tail;
|
|
|
|
tail += 1;
|
|
|
|
|
|
|
|
if (tail >= format.len) {
|
|
|
|
@compileError("expected closing `}` or another `{` after opening `{`");
|
|
|
|
}
|
|
|
|
|
|
|
|
std.debug.assert(tail < format.len);
|
|
|
|
|
|
|
|
inline while (format[tail] != '}') {
|
|
|
|
tail += 1;
|
|
|
|
|
|
|
|
std.debug.assert(tail < format.len);
|
|
|
|
}
|
|
|
|
|
|
|
|
try print_formatted_value(writer, @field(args, format[head .. tail]));
|
|
|
|
|
|
|
|
tail += 1;
|
|
|
|
head = tail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-06 03:25:09 +01:00
|
|
|
try io.write_all(writer, format[head .. ]);
|
2024-05-29 19:27:02 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
else => @compileError("`arguments` must be a struct type"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-06 03:25:09 +01:00
|
|
|
noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void {
|
2024-05-29 19:27:02 +01:00
|
|
|
const Value = @TypeOf(value);
|
|
|
|
|
|
|
|
return switch (@typeInfo(Value)) {
|
|
|
|
.Int => ascii.DecimalFormat.default.print(writer, value),
|
|
|
|
.Float => ascii.DecimalFormat.default.print(writer, value),
|
|
|
|
.Enum => io.print(writer, @tagName(value)),
|
|
|
|
|
|
|
|
.Pointer => |pointer| switch (pointer.size) {
|
2024-07-06 03:25:09 +01:00
|
|
|
.Many, .C => ascii.HexadecimalFormat.default.format(writer, @intFromPtr(value)),
|
|
|
|
.One => if (pointer.child == []const u8) io.write_all(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)),
|
|
|
|
.Slice => if (pointer.child == u8) io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
|
2024-05-29 19:27:02 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
else => @compileError(unformattableMessage(Value)),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const root = @This();
|
|
|
|
|
|
|
|
fn unformattableMessage(comptime Value: type) []const u8 {
|
|
|
|
return "type `" ++ @typeName(Value) ++ "` is not formattable with this formatter";
|
|
|
|
}
|