std.fmt: breaking API changes

added adapter to AnyWriter and GenericWriter to help bridge the gap
between old and new API

make std.testing.expectFmt work at compile-time

std.fmt no longer has a dependency on std.unicode. Formatted printing
was never properly unicode-aware. Now it no longer pretends to be.

Breakage/deprecations:
* std.fs.File.reader -> std.fs.File.deprecatedReader
* std.fs.File.writer -> std.fs.File.deprecatedWriter
* std.io.GenericReader -> std.io.Reader
* std.io.GenericWriter -> std.io.Writer
* std.io.AnyReader -> std.io.Reader
* std.io.AnyWriter -> std.io.Writer
* std.fmt.format -> std.fmt.deprecatedFormat
* std.fmt.fmtSliceEscapeLower -> std.ascii.hexEscape
* std.fmt.fmtSliceEscapeUpper -> std.ascii.hexEscape
* std.fmt.fmtSliceHexLower -> {x}
* std.fmt.fmtSliceHexUpper -> {X}
* std.fmt.fmtIntSizeDec -> {B}
* std.fmt.fmtIntSizeBin -> {Bi}
* std.fmt.fmtDuration -> {D}
* std.fmt.fmtDurationSigned -> {D}
* {} -> {f} when there is a format method
* format method signature
  - anytype -> *std.io.Writer
  - inferred error set -> error{WriteFailed}
  - options -> (deleted)
* std.fmt.Formatted
  - now takes context type explicitly
  - no fmt string
This commit is contained in:
Andrew Kelley
2025-06-27 20:05:22 -07:00
parent 0b3f0124dc
commit 0e37ff0d59
161 changed files with 4385 additions and 5847 deletions

View File

@@ -363,149 +363,136 @@ const Allocator = std.mem.Allocator;
/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
///
/// - An empty `{}` format specifier escapes invalid identifiers, identifiers that shadow primitives
/// and the reserved `_` identifier.
/// - Add `p` to the specifier to render identifiers that shadow primitives unescaped.
/// - Add `_` to the specifier to render the reserved `_` identifier unescaped.
/// - `p` and `_` can be combined, e.g. `{p_}`.
/// See also `fmtIdFlags`.
pub fn fmtId(bytes: []const u8) std.fmt.Formatter(FormatId, FormatId.render) {
return .{ .data = .{ .bytes = bytes, .flags = .{} } };
}
/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
///
pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) {
return .{ .data = bytes };
/// See also `fmtId`.
pub fn fmtIdFlags(bytes: []const u8, flags: FormatId.Flags) std.fmt.Formatter(FormatId, FormatId.render) {
return .{ .data = .{ .bytes = bytes, .flags = flags } };
}
pub fn fmtIdPU(bytes: []const u8) std.fmt.Formatter(FormatId, FormatId.render) {
return .{ .data = .{ .bytes = bytes, .flags = .{ .allow_primitive = true, .allow_underscore = true } } };
}
pub fn fmtIdP(bytes: []const u8) std.fmt.Formatter(FormatId, FormatId.render) {
return .{ .data = .{ .bytes = bytes, .flags = .{ .allow_primitive = true } } };
}
test fmtId {
const expectFmt = std.testing.expectFmt;
try expectFmt("@\"while\"", "{}", .{fmtId("while")});
try expectFmt("@\"while\"", "{p}", .{fmtId("while")});
try expectFmt("@\"while\"", "{_}", .{fmtId("while")});
try expectFmt("@\"while\"", "{p_}", .{fmtId("while")});
try expectFmt("@\"while\"", "{_p}", .{fmtId("while")});
try expectFmt("@\"while\"", "{f}", .{fmtId("while")});
try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_primitive = true })});
try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_underscore = true })});
try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_primitive = true, .allow_underscore = true })});
try expectFmt("hello", "{}", .{fmtId("hello")});
try expectFmt("hello", "{p}", .{fmtId("hello")});
try expectFmt("hello", "{_}", .{fmtId("hello")});
try expectFmt("hello", "{p_}", .{fmtId("hello")});
try expectFmt("hello", "{_p}", .{fmtId("hello")});
try expectFmt("hello", "{f}", .{fmtId("hello")});
try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_primitive = true })});
try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_underscore = true })});
try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_primitive = true, .allow_underscore = true })});
try expectFmt("@\"type\"", "{}", .{fmtId("type")});
try expectFmt("type", "{p}", .{fmtId("type")});
try expectFmt("@\"type\"", "{_}", .{fmtId("type")});
try expectFmt("type", "{p_}", .{fmtId("type")});
try expectFmt("type", "{_p}", .{fmtId("type")});
try expectFmt("@\"type\"", "{f}", .{fmtId("type")});
try expectFmt("type", "{f}", .{fmtIdFlags("type", .{ .allow_primitive = true })});
try expectFmt("@\"type\"", "{f}", .{fmtIdFlags("type", .{ .allow_underscore = true })});
try expectFmt("type", "{f}", .{fmtIdFlags("type", .{ .allow_primitive = true, .allow_underscore = true })});
try expectFmt("@\"_\"", "{}", .{fmtId("_")});
try expectFmt("@\"_\"", "{p}", .{fmtId("_")});
try expectFmt("_", "{_}", .{fmtId("_")});
try expectFmt("_", "{p_}", .{fmtId("_")});
try expectFmt("_", "{_p}", .{fmtId("_")});
try expectFmt("@\"_\"", "{f}", .{fmtId("_")});
try expectFmt("@\"_\"", "{f}", .{fmtIdFlags("_", .{ .allow_primitive = true })});
try expectFmt("_", "{f}", .{fmtIdFlags("_", .{ .allow_underscore = true })});
try expectFmt("_", "{f}", .{fmtIdFlags("_", .{ .allow_primitive = true, .allow_underscore = true })});
try expectFmt("@\"i123\"", "{}", .{fmtId("i123")});
try expectFmt("i123", "{p}", .{fmtId("i123")});
try expectFmt("@\"4four\"", "{}", .{fmtId("4four")});
try expectFmt("_underscore", "{}", .{fmtId("_underscore")});
try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")});
try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")});
try expectFmt("@\"i123\"", "{f}", .{fmtId("i123")});
try expectFmt("i123", "{f}", .{fmtIdFlags("i123", .{ .allow_primitive = true })});
try expectFmt("@\"4four\"", "{f}", .{fmtId("4four")});
try expectFmt("_underscore", "{f}", .{fmtId("_underscore")});
try expectFmt("@\"11\\\"23\"", "{f}", .{fmtId("11\"23")});
try expectFmt("@\"11\\x0f23\"", "{f}", .{fmtId("11\x0F23")});
// These are technically not currently legal in Zig.
try expectFmt("@\"\"", "{}", .{fmtId("")});
try expectFmt("@\"\\x00\"", "{}", .{fmtId("\x00")});
try expectFmt("@\"\"", "{f}", .{fmtId("")});
try expectFmt("@\"\\x00\"", "{f}", .{fmtId("\x00")});
}
/// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.
fn formatId(
pub const FormatId = struct {
bytes: []const u8,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
const allow_primitive, const allow_underscore = comptime parse_fmt: {
var allow_primitive = false;
var allow_underscore = false;
for (fmt) |char| {
switch (char) {
'p' => if (!allow_primitive) {
allow_primitive = true;
continue;
},
'_' => if (!allow_underscore) {
allow_underscore = true;
continue;
},
else => {},
}
@compileError("expected {}, {p}, {_}, {p_} or {_p}, found {" ++ fmt ++ "}");
}
break :parse_fmt .{ allow_primitive, allow_underscore };
flags: Flags,
pub const Flags = struct {
allow_primitive: bool = false,
allow_underscore: bool = false,
};
if (isValidId(bytes) and
(allow_primitive or !std.zig.isPrimitive(bytes)) and
(allow_underscore or !isUnderscore(bytes)))
{
return writer.writeAll(bytes);
/// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.
fn render(ctx: FormatId, writer: *std.io.Writer) std.io.Writer.Error!void {
const bytes = ctx.bytes;
if (isValidId(bytes) and
(ctx.flags.allow_primitive or !std.zig.isPrimitive(bytes)) and
(ctx.flags.allow_underscore or !isUnderscore(bytes)))
{
return writer.writeAll(bytes);
}
try writer.writeAll("@\"");
try stringEscape(bytes, writer);
try writer.writeByte('"');
}
try writer.writeAll("@\"");
try stringEscape(bytes, "", options, writer);
try writer.writeByte('"');
}
};
/// Return a Formatter for Zig Escapes of a double quoted string.
/// The format specifier must be one of:
/// * `{}` treats contents as a double-quoted string.
/// * `{'}` treats contents as a single-quoted string.
pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(stringEscape) {
/// Return a formatter for escaping a double quoted Zig string.
pub fn fmtString(bytes: []const u8) std.fmt.Formatter([]const u8, stringEscape) {
return .{ .data = bytes };
}
test fmtEscapes {
const expectFmt = std.testing.expectFmt;
try expectFmt("\\x0f", "{}", .{fmtEscapes("\x0f")});
try expectFmt(
\\" \\ hi \x07 \x11 " derp \'"
, "\"{'}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")});
try expectFmt(
\\" \\ hi \x07 \x11 \" derp '"
, "\"{}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")});
/// Return a formatter for escaping a single quoted Zig string.
pub fn fmtChar(bytes: []const u8) std.fmt.Formatter([]const u8, charEscape) {
return .{ .data = bytes };
}
/// Print the string as escaped contents of a double quoted or single-quoted string.
/// Format `{}` treats contents as a double-quoted string.
/// Format `{'}` treats contents as a single-quoted string.
pub fn stringEscape(
bytes: []const u8,
comptime f: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
test fmtString {
try std.testing.expectFmt("\\x0f", "{f}", .{fmtString("\x0f")});
try std.testing.expectFmt(
\\" \\ hi \x07 \x11 \" derp '"
, "\"{f}\"", .{fmtString(" \\ hi \x07 \x11 \" derp '")});
}
test fmtChar {
try std.testing.expectFmt(
\\" \\ hi \x07 \x11 " derp \'"
, "\"{f}\"", .{fmtChar(" \\ hi \x07 \x11 \" derp '")});
}
/// Print the string as escaped contents of a double quoted string.
pub fn stringEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!void {
for (bytes) |byte| switch (byte) {
'\n' => try writer.writeAll("\\n"),
'\r' => try writer.writeAll("\\r"),
'\t' => try writer.writeAll("\\t"),
'\\' => try writer.writeAll("\\\\"),
'"' => {
if (f.len == 1 and f[0] == '\'') {
try writer.writeByte('"');
} else if (f.len == 0) {
try writer.writeAll("\\\"");
} else {
@compileError("expected {} or {'}, found {" ++ f ++ "}");
}
},
'\'' => {
if (f.len == 1 and f[0] == '\'') {
try writer.writeAll("\\'");
} else if (f.len == 0) {
try writer.writeByte('\'');
} else {
@compileError("expected {} or {'}, found {" ++ f ++ "}");
}
},
' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte),
// Use hex escapes for rest any unprintable characters.
'\n' => try w.writeAll("\\n"),
'\r' => try w.writeAll("\\r"),
'\t' => try w.writeAll("\\t"),
'\\' => try w.writeAll("\\\\"),
'"' => try w.writeAll("\\\""),
'\'' => try w.writeByte('\''),
' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte),
else => {
try writer.writeAll("\\x");
try std.fmt.formatInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }, writer);
try w.writeAll("\\x");
try w.printIntOptions(byte, 16, .lower, .{ .width = 2, .fill = '0' });
},
};
}
/// Print the string as escaped contents of a single-quoted string.
pub fn charEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!void {
for (bytes) |byte| switch (byte) {
'\n' => try w.writeAll("\\n"),
'\r' => try w.writeAll("\\r"),
'\t' => try w.writeAll("\\t"),
'\\' => try w.writeAll("\\\\"),
'"' => try w.writeByte('"'),
'\'' => try w.writeAll("\\'"),
' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte),
else => {
try w.writeAll("\\x");
try w.printIntOptions(byte, 16, .lower, .{ .width = 2, .fill = '0' });
},
};
}