std: fmt.format to io.Writer.print

allows reverting format -> deprecatedFormat, plus I think this is a
nicer place for the function.
This commit is contained in:
Andrew Kelley 2025-07-09 15:31:02 -07:00
parent 4d93545ded
commit 93ac76594a
9 changed files with 215 additions and 219 deletions

View file

@ -1737,11 +1737,11 @@ test "NIST KAT test" {
var f = sha2.Sha256.init(.{});
const fw = f.writer();
var g = NistDRBG.init(seed);
try std.fmt.deprecatedFormat(fw, "# {s}\n\n", .{mode.name});
try std.fmt.format(fw, "# {s}\n\n", .{mode.name});
for (0..100) |i| {
g.fill(&seed);
try std.fmt.deprecatedFormat(fw, "count = {}\n", .{i});
try std.fmt.deprecatedFormat(fw, "seed = {X}\n", .{&seed});
try std.fmt.format(fw, "count = {}\n", .{i});
try std.fmt.format(fw, "seed = {X}\n", .{&seed});
var g2 = NistDRBG.init(seed);
// This is not equivalent to g2.fill(kseed[:]). As the reference
@ -1756,10 +1756,10 @@ test "NIST KAT test" {
const e = kp.public_key.encaps(eseed);
const ss2 = try kp.secret_key.decaps(&e.ciphertext);
try testing.expectEqual(ss2, e.shared_secret);
try std.fmt.deprecatedFormat(fw, "pk = {X}\n", .{&kp.public_key.toBytes()});
try std.fmt.deprecatedFormat(fw, "sk = {X}\n", .{&kp.secret_key.toBytes()});
try std.fmt.deprecatedFormat(fw, "ct = {X}\n", .{&e.ciphertext});
try std.fmt.deprecatedFormat(fw, "ss = {X}\n\n", .{&e.shared_secret});
try std.fmt.format(fw, "pk = {X}\n", .{&kp.public_key.toBytes()});
try std.fmt.format(fw, "sk = {X}\n", .{&kp.secret_key.toBytes()});
try std.fmt.format(fw, "ct = {X}\n", .{&e.ciphertext});
try std.fmt.format(fw, "ss = {X}\n\n", .{&e.shared_secret});
}
var out: [32]u8 = undefined;

View file

@ -78,197 +78,10 @@ pub const Number = struct {
};
};
/// Renders fmt string with args, calling `writer` with slices of bytes.
/// If `writer` returns an error, the error is returned from `format` and
/// `writer` is not called again.
///
/// The format string must be comptime-known and may contain placeholders following
/// this format:
/// `{[argument][specifier]:[fill][alignment][width].[precision]}`
///
/// Above, each word including its surrounding [ and ] is a parameter which you have to replace with something:
///
/// - *argument* is either the numeric index or the field name of the argument that should be inserted
/// - when using a field name, you are required to enclose the field name (an identifier) in square
/// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...}
/// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below)
/// - *fill* is a single byte which is used to pad formatted numbers.
/// - *alignment* is one of the three bytes '<', '^', or '>' to make numbers
/// left, center, or right-aligned, respectively.
/// - Not all specifiers support alignment.
/// - Alignment is not Unicode-aware; appropriate only when used with raw bytes or ASCII.
/// - *width* is the total width of the field in bytes. This only applies to number formatting.
/// - *precision* specifies how many decimals a formatted number should have.
///
/// Note that most of the parameters are optional and may be omitted. Also you
/// can leave out separators like `:` and `.` when all parameters after the
/// separator are omitted.
///
/// Only exception is the *fill* parameter. If a non-zero *fill* character is
/// required at the same time as *width* is specified, one has to specify
/// *alignment* as well, as otherwise the digit following `:` is interpreted as
/// *width*, not *fill*.
///
/// The *specifier* has several options for types:
/// - `x` and `X`: output numeric value in hexadecimal notation, or string in hexadecimal bytes
/// - `s`:
/// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination
/// - for slices of u8, print the entire slice as a string without zero-termination
/// - `t`:
/// - for enums and tagged unions: prints the tag name
/// - for error sets: prints the error name
/// - `b64`: output string as standard base64
/// - `e`: output floating point value in scientific notation
/// - `d`: output numeric value in decimal notation
/// - `b`: output integer value in binary notation
/// - `o`: output integer value in octal notation
/// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max.
/// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max.
/// - `D`: output nanoseconds as duration
/// - `B`: output bytes in SI units (decimal)
/// - `Bi`: output bytes in IEC units (binary)
/// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value.
/// - `!`: output error union value as either the unwrapped value, or the formatted error value; may be followed by a format specifier for the underlying value.
/// - `*`: output the address of the value instead of the value itself.
/// - `any`: output a value of any type using its default format.
/// - `f`: delegates to a method on the type named "format" with the signature `fn (*Writer, args: anytype) Writer.Error!void`.
///
/// A user type may be a `struct`, `vector`, `union` or `enum` type.
///
/// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`.
pub fn format(w: *Writer, comptime fmt: []const u8, args: anytype) Writer.Error!void {
const ArgsType = @TypeOf(args);
const args_type_info = @typeInfo(ArgsType);
if (args_type_info != .@"struct") {
@compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
}
const fields_info = args_type_info.@"struct".fields;
if (fields_info.len > max_format_args) {
@compileError("32 arguments max are supported per format call");
}
@setEvalBranchQuota(fmt.len * 1000);
comptime var arg_state: ArgState = .{ .args_len = fields_info.len };
comptime var i = 0;
comptime var literal: []const u8 = "";
inline while (true) {
const start_index = i;
inline while (i < fmt.len) : (i += 1) {
switch (fmt[i]) {
'{', '}' => break,
else => {},
}
}
comptime var end_index = i;
comptime var unescape_brace = false;
// Handle {{ and }}, those are un-escaped as single braces
if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
unescape_brace = true;
// Make the first brace part of the literal...
end_index += 1;
// ...and skip both
i += 2;
}
literal = literal ++ fmt[start_index..end_index];
// We've already skipped the other brace, restart the loop
if (unescape_brace) continue;
// Write out the literal
if (literal.len != 0) {
try w.writeAll(literal);
literal = "";
}
if (i >= fmt.len) break;
if (fmt[i] == '}') {
@compileError("missing opening {");
}
// Get past the {
comptime assert(fmt[i] == '{');
i += 1;
const fmt_begin = i;
// Find the closing brace
inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
const fmt_end = i;
if (i >= fmt.len) {
@compileError("missing closing }");
}
// Get past the }
comptime assert(fmt[i] == '}');
i += 1;
const placeholder_array = fmt[fmt_begin..fmt_end].*;
const placeholder = comptime Placeholder.parse(&placeholder_array);
const arg_pos = comptime switch (placeholder.arg) {
.none => null,
.number => |pos| pos,
.named => |arg_name| meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'"),
};
const width = switch (placeholder.width) {
.none => null,
.number => |v| v,
.named => |arg_name| blk: {
const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'");
_ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
break :blk @field(args, arg_name);
},
};
const precision = switch (placeholder.precision) {
.none => null,
.number => |v| v,
.named => |arg_name| blk: {
const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'");
_ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
break :blk @field(args, arg_name);
},
};
const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
@compileError("too few arguments");
try w.printValue(
placeholder.specifier_arg,
.{
.fill = placeholder.fill,
.alignment = placeholder.alignment,
.width = width,
.precision = precision,
},
@field(args, fields_info[arg_to_print].name),
std.options.fmt_max_depth,
);
}
if (comptime arg_state.hasUnusedArgs()) {
const missing_count = arg_state.args_len - @popCount(arg_state.used_args);
switch (missing_count) {
0 => unreachable,
1 => @compileError("unused argument in '" ++ fmt ++ "'"),
else => @compileError(comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
}
}
}
/// Deprecated in favor of `format`.
pub fn deprecatedFormat(writer: anytype, comptime fmt: []const u8, args: anytype) !void {
/// Deprecated in favor of `Writer.print`.
pub fn format(writer: anytype, comptime fmt: []const u8, args: anytype) !void {
var adapter = writer.adaptToNewApi();
return format(&adapter.new_interface, fmt, args) catch |err| switch (err) {
return adapter.new_interface.print(fmt, args) catch |err| switch (err) {
error.WriteFailed => return adapter.err.?,
};
}
@ -418,7 +231,6 @@ pub const Parser = struct {
};
pub const ArgSetType = u32;
const max_format_args = @typeInfo(ArgSetType).int.bits;
pub const ArgState = struct {
next_arg: usize = 0,
@ -1524,8 +1336,8 @@ test "recursive format function" {
pub fn format(self: R, writer: *Writer) Writer.Error!void {
return switch (self) {
.Leaf => |n| std.fmt.format(writer, "Leaf({})", .{n}),
.Branch => |b| std.fmt.format(writer, "Branch({f}, {f})", .{ b.left, b.right }),
.Leaf => |n| writer.print("Leaf({})", .{n}),
.Branch => |b| writer.print("Branch({f}, {f})", .{ b.left, b.right }),
};
}
};

View file

@ -21,7 +21,7 @@ pub fn writeAll(self: Self, bytes: []const u8) anyerror!void {
}
pub fn print(self: Self, comptime format: []const u8, args: anytype) anyerror!void {
return std.fmt.deprecatedFormat(self, format, args);
return std.fmt.format(self, format, args);
}
pub fn writeByte(self: Self, byte: u8) anyerror!void {

View file

@ -519,8 +519,192 @@ pub fn writeAllPreserve(w: *Writer, preserve_length: usize, bytes: []const u8) E
while (index < bytes.len) index += try w.writePreserve(preserve_length, bytes[index..]);
}
pub fn print(w: *Writer, comptime format: []const u8, args: anytype) Error!void {
try std.fmt.format(w, format, args);
/// Renders fmt string with args, calling `writer` with slices of bytes.
/// If `writer` returns an error, the error is returned from `format` and
/// `writer` is not called again.
///
/// The format string must be comptime-known and may contain placeholders following
/// this format:
/// `{[argument][specifier]:[fill][alignment][width].[precision]}`
///
/// Above, each word including its surrounding [ and ] is a parameter which you have to replace with something:
///
/// - *argument* is either the numeric index or the field name of the argument that should be inserted
/// - when using a field name, you are required to enclose the field name (an identifier) in square
/// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...}
/// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below)
/// - *fill* is a single byte which is used to pad formatted numbers.
/// - *alignment* is one of the three bytes '<', '^', or '>' to make numbers
/// left, center, or right-aligned, respectively.
/// - Not all specifiers support alignment.
/// - Alignment is not Unicode-aware; appropriate only when used with raw bytes or ASCII.
/// - *width* is the total width of the field in bytes. This only applies to number formatting.
/// - *precision* specifies how many decimals a formatted number should have.
///
/// Note that most of the parameters are optional and may be omitted. Also you
/// can leave out separators like `:` and `.` when all parameters after the
/// separator are omitted.
///
/// Only exception is the *fill* parameter. If a non-zero *fill* character is
/// required at the same time as *width* is specified, one has to specify
/// *alignment* as well, as otherwise the digit following `:` is interpreted as
/// *width*, not *fill*.
///
/// The *specifier* has several options for types:
/// - `x` and `X`: output numeric value in hexadecimal notation, or string in hexadecimal bytes
/// - `s`:
/// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination
/// - for slices of u8, print the entire slice as a string without zero-termination
/// - `t`:
/// - for enums and tagged unions: prints the tag name
/// - for error sets: prints the error name
/// - `b64`: output string as standard base64
/// - `e`: output floating point value in scientific notation
/// - `d`: output numeric value in decimal notation
/// - `b`: output integer value in binary notation
/// - `o`: output integer value in octal notation
/// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max.
/// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max.
/// - `D`: output nanoseconds as duration
/// - `B`: output bytes in SI units (decimal)
/// - `Bi`: output bytes in IEC units (binary)
/// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value.
/// - `!`: output error union value as either the unwrapped value, or the formatted error value; may be followed by a format specifier for the underlying value.
/// - `*`: output the address of the value instead of the value itself.
/// - `any`: output a value of any type using its default format.
/// - `f`: delegates to a method on the type named "format" with the signature `fn (*Writer, args: anytype) Writer.Error!void`.
///
/// A user type may be a `struct`, `vector`, `union` or `enum` type.
///
/// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`.
pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void {
const ArgsType = @TypeOf(args);
const args_type_info = @typeInfo(ArgsType);
if (args_type_info != .@"struct") {
@compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
}
const fields_info = args_type_info.@"struct".fields;
const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits;
if (fields_info.len > max_format_args) {
@compileError("32 arguments max are supported per format call");
}
@setEvalBranchQuota(fmt.len * 1000);
comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len };
comptime var i = 0;
comptime var literal: []const u8 = "";
inline while (true) {
const start_index = i;
inline while (i < fmt.len) : (i += 1) {
switch (fmt[i]) {
'{', '}' => break,
else => {},
}
}
comptime var end_index = i;
comptime var unescape_brace = false;
// Handle {{ and }}, those are un-escaped as single braces
if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
unescape_brace = true;
// Make the first brace part of the literal...
end_index += 1;
// ...and skip both
i += 2;
}
literal = literal ++ fmt[start_index..end_index];
// We've already skipped the other brace, restart the loop
if (unescape_brace) continue;
// Write out the literal
if (literal.len != 0) {
try w.writeAll(literal);
literal = "";
}
if (i >= fmt.len) break;
if (fmt[i] == '}') {
@compileError("missing opening {");
}
// Get past the {
comptime assert(fmt[i] == '{');
i += 1;
const fmt_begin = i;
// Find the closing brace
inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
const fmt_end = i;
if (i >= fmt.len) {
@compileError("missing closing }");
}
// Get past the }
comptime assert(fmt[i] == '}');
i += 1;
const placeholder_array = fmt[fmt_begin..fmt_end].*;
const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array);
const arg_pos = comptime switch (placeholder.arg) {
.none => null,
.number => |pos| pos,
.named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'"),
};
const width = switch (placeholder.width) {
.none => null,
.number => |v| v,
.named => |arg_name| blk: {
const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'");
_ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
break :blk @field(args, arg_name);
},
};
const precision = switch (placeholder.precision) {
.none => null,
.number => |v| v,
.named => |arg_name| blk: {
const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'");
_ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
break :blk @field(args, arg_name);
},
};
const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
@compileError("too few arguments");
try w.printValue(
placeholder.specifier_arg,
.{
.fill = placeholder.fill,
.alignment = placeholder.alignment,
.width = width,
.precision = precision,
},
@field(args, fields_info[arg_to_print].name),
std.options.fmt_max_depth,
);
}
if (comptime arg_state.hasUnusedArgs()) {
const missing_count = arg_state.args_len - @popCount(arg_state.used_args);
switch (missing_count) {
0 => unreachable,
1 => @compileError("unused argument in '" ++ fmt ++ "'"),
else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
}
}
}
/// Calls `drain` as many times as necessary such that `byte` is transferred.

View file

@ -689,7 +689,7 @@ fn outputUnicodeEscape(codepoint: u21, out_stream: anytype) !void {
// by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
try out_stream.writeAll("\\u");
//try w.printInt("x", .{ .width = 4, .fill = '0' }, codepoint);
try std.fmt.deprecatedFormat(out_stream, "{x:0>4}", .{codepoint});
try std.fmt.format(out_stream, "{x:0>4}", .{codepoint});
} else {
assert(codepoint <= 0x10FFFF);
// To escape an extended character that is not in the Basic Multilingual Plane,
@ -698,10 +698,10 @@ fn outputUnicodeEscape(codepoint: u21, out_stream: anytype) !void {
const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00;
try out_stream.writeAll("\\u");
//try w.printInt("x", .{ .width = 4, .fill = '0' }, high);
try std.fmt.deprecatedFormat(out_stream, "{x:0>4}", .{high});
try std.fmt.format(out_stream, "{x:0>4}", .{high});
try out_stream.writeAll("\\u");
//try w.printInt("x", .{ .width = 4, .fill = '0' }, low);
try std.fmt.deprecatedFormat(out_stream, "{x:0>4}", .{low});
try std.fmt.format(out_stream, "{x:0>4}", .{low});
}
}

View file

@ -65,7 +65,7 @@ pub const Guid = extern struct {
const time_mid = @byteSwap(self.time_mid);
const time_high_and_version = @byteSwap(self.time_high_and_version);
return std.fmt.format(writer, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{
return writer.print("{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{
std.mem.asBytes(&time_low),
std.mem.asBytes(&time_mid),
std.mem.asBytes(&time_high_and_version),

View file

@ -2872,7 +2872,7 @@ fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void {
.success => |codepoint| {
if (codepoint <= 0x7f) {
const buf = [1]u8{@as(u8, @intCast(codepoint))};
try std.fmt.deprecatedFormat(writer, "{f}", .{std.zig.fmtString(&buf)});
try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)});
} else {
try writer.writeAll(escape_sequence);
}
@ -2884,7 +2884,7 @@ fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void {
},
0x00...('\\' - 1), ('\\' + 1)...0x7f => {
const buf = [1]u8{byte};
try std.fmt.deprecatedFormat(writer, "{f}", .{std.zig.fmtString(&buf)});
try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)});
pos += 1;
},
0x80...0xff => {

View file

@ -501,7 +501,7 @@ pub fn Serializer(Writer: type) type {
try self.int(val);
},
.float, .comptime_float => try self.float(val),
.bool, .null => try std.fmt.deprecatedFormat(self.writer, "{}", .{val}),
.bool, .null => try std.fmt.format(self.writer, "{}", .{val}),
.enum_literal => try self.ident(@tagName(val)),
.@"enum" => try self.ident(@tagName(val)),
.pointer => |pointer| {
@ -616,7 +616,7 @@ pub fn Serializer(Writer: type) type {
/// Serialize an integer.
pub fn int(self: *Self, val: anytype) Writer.Error!void {
//try self.writer.printInt(val, 10, .lower, .{});
try std.fmt.deprecatedFormat(self.writer, "{d}", .{val});
try std.fmt.format(self.writer, "{d}", .{val});
}
/// Serialize a float.
@ -631,12 +631,12 @@ pub fn Serializer(Writer: type) type {
} else if (std.math.isNegativeZero(val)) {
return self.writer.writeAll("-0.0");
} else {
try std.fmt.deprecatedFormat(self.writer, "{d}", .{val});
try std.fmt.format(self.writer, "{d}", .{val});
},
.comptime_float => if (val == 0) {
return self.writer.writeAll("0");
} else {
try std.fmt.deprecatedFormat(self.writer, "{d}", .{val});
try std.fmt.format(self.writer, "{d}", .{val});
},
else => comptime unreachable,
}
@ -659,7 +659,7 @@ pub fn Serializer(Writer: type) type {
var buf: [8]u8 = undefined;
const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint;
const str = buf[0..len];
try std.fmt.deprecatedFormat(self.writer, "'{f}'", .{std.zig.fmtChar(str)});
try std.fmt.format(self.writer, "'{f}'", .{std.zig.fmtChar(str)});
}
/// Like `value`, but always serializes `val` as a tuple.
@ -717,7 +717,7 @@ pub fn Serializer(Writer: type) type {
/// Like `value`, but always serializes `val` as a string.
pub fn string(self: *Self, val: []const u8) Writer.Error!void {
try std.fmt.deprecatedFormat(self.writer, "\"{f}\"", .{std.zig.fmtString(val)});
try std.fmt.format(self.writer, "\"{f}\"", .{std.zig.fmtString(val)});
}
/// Options for formatting multiline strings.

View file

@ -83,15 +83,15 @@ pub const Node = struct {
pub fn format(self: *const Doc, writer: *std.io.Writer) std.io.Writer.Error!void {
if (self.directive) |id| {
try std.fmt.format(writer, "{{ ", .{});
try writer.print("{{ ", .{});
const directive = self.base.tree.getRaw(id, id);
try std.fmt.format(writer, ".directive = {s}, ", .{directive});
try writer.print(".directive = {s}, ", .{directive});
}
if (self.value) |node| {
try std.fmt.format(writer, "{}", .{node});
try writer.print("{}", .{node});
}
if (self.directive != null) {
try std.fmt.format(writer, " }}", .{});
try writer.print(" }}", .{});
}
}
};