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(); print_formatted(buffer.writer(), format, args) catch unreachable; return buffer.to_allocation(formatted_len, 0); } pub fn count_formatted(comptime format: []const u8, args: anytype) usize { var count = io.NullWritable{}; write_formatted(count.writer(), format, args) catch unreachable; return count.written; } 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 { 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"); } try io.write_all(writer, format[head .. (tail - 1)]); 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; } } } } try io.write_all(writer, format[head .. ]); }, else => @compileError("`arguments` must be a struct type"), } } noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void { 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) { .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)), }, else => @compileError(unformattableMessage(Value)), }; } const root = @This(); fn unformattableMessage(comptime Value: type) []const u8 { return "type `" ++ @typeName(Value) ++ "` is not formattable with this formatter"; }