authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-20 16:23:10-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-20 17:35:12-07:00
log7e012b4f7c08e234b7e13cd7875a3a496dd80956
treeac979640c449a671a01a6de7bdc93730d7e1675c
parentf37a9103562116a19c21e3f2f392b9217fca63ab

std.Io.Writer: introduce {qf}

calls format function and then double quote escapes it

2 files changed, 79 insertions(+), 6 deletions(-)

lib/std/Io/Writer.zig+17
...@@ -1231,6 +1231,18 @@ pub fn printValue(...@@ -1231,6 +1231,18 @@ pub fn printValue(
1231 },1231 },
1232 else => {},1232 else => {},
1233 },1233 },
1234 'q' => switch (fmt[1]) {
1235 'f' => {
1236 try w.writeByte('"');
1237 var buffer: [64]u8 = undefined;
1238 var escaping_writer: std.zig.StringEscapeWriter = .init(w, &buffer);
1239 try value.format(&escaping_writer.writer);
1240 try escaping_writer.writer.flush();
1241 try w.writeByte('"');
1242 return;
1243 },
1244 else => {},
1245 },
1234 else => {},1246 else => {},
1235 },1247 },
1236 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) {1248 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) {
...@@ -2143,6 +2155,11 @@ test "{q} format string" {...@@ -2143,6 +2155,11 @@ test "{q} format string" {
2143 try testing.expectFmt("hello \"i\\tlike\\\"cheese\\x00\\x05cheese\" world", "hello {q} world", .{data});2155 try testing.expectFmt("hello \"i\\tlike\\\"cheese\\x00\\x05cheese\" world", "hello {q} world", .{data});
2144}2156}
21452157
2158test "{qf} format string" {
2159 const data: []const u8 = "😎";
2160 try testing.expectFmt("hello \"@\\\"😎\\\"\" world", "hello {qf} world", .{std.zig.fmtId(data)});
2161}
2162
2146fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {2163fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
2147 var buffer: [100]u8 = undefined;2164 var buffer: [100]u8 = undefined;
2148 var w: Writer = .fixed(&buffer);2165 var w: Writer = .fixed(&buffer);
lib/std/zig.zig+62-6
...@@ -545,20 +545,76 @@ test fmtChar {...@@ -545,20 +545,76 @@ test fmtChar {
545///545///
546/// Everything else is passed through unmodified.546/// Everything else is passed through unmodified.
547pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void {547pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void {
548 _ = try stringEscapeCounting(bytes, w);
549}
550
551pub fn stringEscapeCounting(bytes: []const u8, w: *Writer) Writer.Error!usize {
552 var n: usize = 0;
548 for (bytes) |byte| switch (byte) {553 for (bytes) |byte| switch (byte) {
549 '\t' => try w.writeAll("\\t"),554 '\t' => {
550 '\n' => try w.writeAll("\\n"),555 try w.writeAll("\\t");
551 '\r' => try w.writeAll("\\r"),556 n += 2;
552 '\\' => try w.writeAll("\\\\"),557 },
553 '"' => try w.writeAll("\\\""),558 '\n' => {
559 try w.writeAll("\\n");
560 n += 2;
561 },
562 '\r' => {
563 try w.writeAll("\\r");
564 n += 2;
565 },
566 '\\' => {
567 try w.writeAll("\\\\");
568 n += 2;
569 },
570 '"' => {
571 try w.writeAll("\\\"");
572 n += 2;
573 },
554 0...8, 11, 12, 14...0x1f, 0x7f => {574 0...8, 11, 12, 14...0x1f, 0x7f => {
555 try w.writeAll("\\x");575 try w.writeAll("\\x");
556 try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });576 try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });
577 n += 4;
578 },
579 else => {
580 try w.writeByte(byte);
581 n += 1;
557 },582 },
558 else => try w.writeByte(byte),
559 };583 };
584 return n;
560}585}
561586
587pub const StringEscapeWriter = struct {
588 out: *Writer,
589 writer: Writer,
590
591 pub fn init(out: *Writer, buffer: []u8) @This() {
592 return .{
593 .out = out,
594 .writer = .{
595 .vtable = &.{ .drain = @This().drain },
596 .buffer = buffer,
597 },
598 };
599 }
600
601 fn drain(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
602 const sew: *StringEscapeWriter = @alignCast(@fieldParentPtr("writer", w));
603 const out = sew.out;
604 _ = try stringEscapeCounting(w.buffered(), out);
605 w.end = 0;
606 var n: usize = 0;
607 for (data[0 .. data.len - 1]) |bytes| {
608 n += try stringEscapeCounting(bytes, out);
609 }
610 const pattern = data[data.len - 1];
611 for (0..splat) |_| {
612 n += try stringEscapeCounting(pattern, out);
613 }
614 return n;
615 }
616};
617
562/// Print as escaped contents of a single-quoted string.618/// Print as escaped contents of a single-quoted string.
563pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void {619pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void {
564 switch (codepoint) {620 switch (codepoint) {