authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-10-17 21:06:54-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-10-17 21:06:54-04:00
loge51bc19e4a45211476491f29d2beff73ff6be570
tree8558a6b83f13526edb618f8c8e968a69b34f615a
parent71ac5b151524288562bb78d9b0924bb3b0ba5e1c
parente8ca1b254d41d5711dc5294d99b8d81c74f36add
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #6394 from Vexu/fmt

std.fmt add specifier for printing Zig identifiers

7 files changed, 94 insertions(+), 121 deletions(-)

lib/std/build.zig+9-16
...@@ -1767,26 +1767,21 @@ pub const LibExeObjStep = struct {...@@ -1767,26 +1767,21 @@ pub const LibExeObjStep = struct {
1767 const out = self.build_options_contents.outStream();1767 const out = self.build_options_contents.outStream();
1768 switch (T) {1768 switch (T) {
1769 []const []const u8 => {1769 []const []const u8 => {
1770 out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable;1770 out.print("pub const {z}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable;
1771 for (value) |slice| {1771 for (value) |slice| {
1772 out.writeAll(" ") catch unreachable;1772 out.print(" \"{Z}\",\n", .{slice}) catch unreachable;
1773 std.zig.renderStringLiteral(slice, out) catch unreachable;
1774 out.writeAll(",\n") catch unreachable;
1775 }1773 }
1776 out.writeAll("};\n") catch unreachable;1774 out.writeAll("};\n") catch unreachable;
1777 return;1775 return;
1778 },1776 },
1779 []const u8 => {1777 []const u8 => {
1780 out.print("pub const {}: []const u8 = ", .{name}) catch unreachable;1778 out.print("pub const {z}: []const u8 = \"{Z}\";\n", .{ name, value }) catch unreachable;
1781 std.zig.renderStringLiteral(value, out) catch unreachable;
1782 out.writeAll(";\n") catch unreachable;
1783 return;1779 return;
1784 },1780 },
1785 ?[]const u8 => {1781 ?[]const u8 => {
1786 out.print("pub const {}: ?[]const u8 = ", .{name}) catch unreachable;1782 out.print("pub const {z}: ?[]const u8 = ", .{name}) catch unreachable;
1787 if (value) |payload| {1783 if (value) |payload| {
1788 std.zig.renderStringLiteral(payload, out) catch unreachable;1784 out.print("\"{Z}\";\n", .{payload}) catch unreachable;
1789 out.writeAll(";\n") catch unreachable;
1790 } else {1785 } else {
1791 out.writeAll("null;\n") catch unreachable;1786 out.writeAll("null;\n") catch unreachable;
1792 }1787 }
...@@ -1796,15 +1791,15 @@ pub const LibExeObjStep = struct {...@@ -1796,15 +1791,15 @@ pub const LibExeObjStep = struct {
1796 }1791 }
1797 switch (@typeInfo(T)) {1792 switch (@typeInfo(T)) {
1798 .Enum => |enum_info| {1793 .Enum => |enum_info| {
1799 out.print("pub const {} = enum {{\n", .{@typeName(T)}) catch unreachable;1794 out.print("pub const {z} = enum {{\n", .{@typeName(T)}) catch unreachable;
1800 inline for (enum_info.fields) |field| {1795 inline for (enum_info.fields) |field| {
1801 out.print(" {},\n", .{field.name}) catch unreachable;1796 out.print(" {z},\n", .{field.name}) catch unreachable;
1802 }1797 }
1803 out.writeAll("};\n") catch unreachable;1798 out.writeAll("};\n") catch unreachable;
1804 },1799 },
1805 else => {},1800 else => {},
1806 }1801 }
1807 out.print("pub const {} = {};\n", .{ name, value }) catch unreachable;1802 out.print("pub const {z} = {};\n", .{ name, value }) catch unreachable;
1808 }1803 }
18091804
1810 /// The value is the path in the cache dir.1805 /// The value is the path in the cache dir.
...@@ -2017,9 +2012,7 @@ pub const LibExeObjStep = struct {...@@ -2017,9 +2012,7 @@ pub const LibExeObjStep = struct {
2017 // Render build artifact options at the last minute, now that the path is known.2012 // Render build artifact options at the last minute, now that the path is known.
2018 for (self.build_options_artifact_args.items) |item| {2013 for (self.build_options_artifact_args.items) |item| {
2019 const out = self.build_options_contents.writer();2014 const out = self.build_options_contents.writer();
2020 out.print("pub const {}: []const u8 = ", .{item.name}) catch unreachable;2015 out.print("pub const {}: []const u8 = \"{Z}\";\n", .{ item.name, item.artifact.getOutputPath() }) catch unreachable;
2021 std.zig.renderStringLiteral(item.artifact.getOutputPath(), out) catch unreachable;
2022 out.writeAll(";\n") catch unreachable;
2023 }2016 }
20242017
2025 const build_options_file = try fs.path.join(2018 const build_options_file = try fs.path.join(
lib/std/fmt.zig+76-6
...@@ -65,6 +65,8 @@ fn peekIsAlign(comptime fmt: []const u8) bool {...@@ -65,6 +65,8 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
65/// - format the non-numeric value as a string of bytes in hexadecimal notation ("binary dump") in either lower case or upper case65/// - format the non-numeric value as a string of bytes in hexadecimal notation ("binary dump") in either lower case or upper case
66/// - output numeric value in hexadecimal notation66/// - output numeric value in hexadecimal notation
67/// - `s`: print a pointer-to-many as a c-string, use zero-termination67/// - `s`: print a pointer-to-many as a c-string, use zero-termination
68/// - `z`: escape the string with @"" syntax if it is not a valid Zig identifier.
69/// - `Z`: print the string escaping non-printable characters using Zig escape sequences.
68/// - `B` and `Bi`: output a memory size in either metric (1000) or power-of-two (1024) based notation. works for both float and integer values.70/// - `B` and `Bi`: output a memory size in either metric (1000) or power-of-two (1024) based notation. works for both float and integer values.
69/// - `e` and `E`: if printing a string, escape non-printable characters71/// - `e` and `E`: if printing a string, escape non-printable characters
70/// - `e`: output floating point value in scientific notation72/// - `e`: output floating point value in scientific notation
...@@ -543,6 +545,13 @@ pub fn formatIntValue(...@@ -543,6 +545,13 @@ pub fn formatIntValue(
543 } else {545 } else {
544 @compileError("Cannot print integer that is larger than 8 bits as a ascii");546 @compileError("Cannot print integer that is larger than 8 bits as a ascii");
545 }547 }
548 } else if (comptime std.mem.eql(u8, fmt, "Z")) {
549 if (@typeInfo(@TypeOf(int_value)).Int.bits <= 8) {
550 const c: u8 = int_value;
551 return formatZigEscapes(@as(*const [1]u8, &c), options, writer);
552 } else {
553 @compileError("Cannot escape character with more than 8 bits");
554 }
546 } else if (comptime std.mem.eql(u8, fmt, "b")) {555 } else if (comptime std.mem.eql(u8, fmt, "b")) {
547 radix = 2;556 radix = 2;
548 uppercase = false;557 uppercase = false;
...@@ -612,6 +621,10 @@ pub fn formatText(...@@ -612,6 +621,10 @@ pub fn formatText(
612 }621 }
613 }622 }
614 return;623 return;
624 } else if (comptime std.mem.eql(u8, fmt, "z")) {
625 return formatZigIdentifier(bytes, options, writer);
626 } else if (comptime std.mem.eql(u8, fmt, "Z")) {
627 return formatZigEscapes(bytes, options, writer);
615 } else {628 } else {
616 @compileError("Unknown format string: '" ++ fmt ++ "'");629 @compileError("Unknown format string: '" ++ fmt ++ "'");
617 }630 }
...@@ -652,9 +665,55 @@ pub fn formatBuf(...@@ -652,9 +665,55 @@ pub fn formatBuf(
652 }665 }
653}666}
654667
655// Print a float in scientific notation to the specified precision. Null uses full precision.668/// Print the string as a Zig identifier escaping it with @"" syntax if needed.
656// It should be the case that every full precision, printed value can be re-parsed back to the669pub fn formatZigIdentifier(
657// same type unambiguously.670 bytes: []const u8,
671 options: FormatOptions,
672 writer: anytype,
673) !void {
674 if (isValidZigIdentifier(bytes)) {
675 return writer.writeAll(bytes);
676 }
677 try writer.writeAll("@\"");
678 try formatZigEscapes(bytes, options, writer);
679 try writer.writeByte('"');
680}
681
682fn isValidZigIdentifier(bytes: []const u8) bool {
683 for (bytes) |c, i| {
684 switch (c) {
685 '_', 'a'...'z', 'A'...'Z' => {},
686 '0'...'9' => if (i == 0) return false,
687 else => return false,
688 }
689 }
690 return std.zig.Token.getKeyword(bytes) == null;
691}
692
693pub fn formatZigEscapes(
694 bytes: []const u8,
695 options: FormatOptions,
696 writer: anytype,
697) !void {
698 for (bytes) |byte| switch (byte) {
699 '\n' => try writer.writeAll("\\n"),
700 '\r' => try writer.writeAll("\\r"),
701 '\t' => try writer.writeAll("\\t"),
702 '\\' => try writer.writeAll("\\\\"),
703 '"' => try writer.writeAll("\\\""),
704 '\'' => try writer.writeAll("\\'"),
705 ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte),
706 // Use hex escapes for rest any unprintable characters.
707 else => {
708 try writer.writeAll("\\x");
709 try formatInt(byte, 16, false, .{ .width = 2, .fill = '0' }, writer);
710 },
711 };
712}
713
714/// Print a float in scientific notation to the specified precision. Null uses full precision.
715/// It should be the case that every full precision, printed value can be re-parsed back to the
716/// same type unambiguously.
658pub fn formatFloatScientific(717pub fn formatFloatScientific(
659 value: anytype,718 value: anytype,
660 options: FormatOptions,719 options: FormatOptions,
...@@ -746,8 +805,8 @@ pub fn formatFloatScientific(...@@ -746,8 +805,8 @@ pub fn formatFloatScientific(
746 }805 }
747}806}
748807
749// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.808/// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
750// By default floats are printed at full precision (no rounding).809/// By default floats are printed at full precision (no rounding).
751pub fn formatFloatDecimal(810pub fn formatFloatDecimal(
752 value: anytype,811 value: anytype,
753 options: FormatOptions,812 options: FormatOptions,
...@@ -1136,7 +1195,7 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr...@@ -1136,7 +1195,7 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr
1136 return result[0 .. result.len - 1 :0];1195 return result[0 .. result.len - 1 :0];
1137}1196}
11381197
1139// Count the characters needed for format. Useful for preallocating memory1198/// Count the characters needed for format. Useful for preallocating memory
1140pub fn count(comptime fmt: []const u8, args: anytype) u64 {1199pub fn count(comptime fmt: []const u8, args: anytype) u64 {
1141 var counting_writer = std.io.countingWriter(std.io.null_writer);1200 var counting_writer = std.io.countingWriter(std.io.null_writer);
1142 format(counting_writer.writer(), fmt, args) catch |err| switch (err) {};1201 format(counting_writer.writer(), fmt, args) catch |err| switch (err) {};
...@@ -1334,6 +1393,17 @@ test "escape non-printable" {...@@ -1334,6 +1393,17 @@ test "escape non-printable" {
1334 try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"});1393 try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"});
1335}1394}
13361395
1396test "escape invalid identifiers" {
1397 try testFmt("@\"while\"", "{z}", .{"while"});
1398 try testFmt("hello", "{z}", .{"hello"});
1399 try testFmt("@\"11\\\"23\"", "{z}", .{"11\"23"});
1400 try testFmt("@\"11\\x0f23\"", "{z}", .{"11\x0F23"});
1401 try testFmt("\\x0f", "{Z}", .{0x0f});
1402 try testFmt(
1403 \\" \\ hi \x07 \x11 \" derp \'"
1404 , "\"{Z}\"", .{" \\ hi \x07 \x11 \" derp '"});
1405}
1406
1337test "pointer" {1407test "pointer" {
1338 {1408 {
1339 const value = @intToPtr(*align(1) i32, 0xdeadbeef);1409 const value = @intToPtr(*align(1) i32, 0xdeadbeef);
lib/std/zig.zig-1
...@@ -11,7 +11,6 @@ pub const Tokenizer = tokenizer.Tokenizer;...@@ -11,7 +11,6 @@ pub const Tokenizer = tokenizer.Tokenizer;
11pub const parse = @import("zig/parse.zig").parse;11pub const parse = @import("zig/parse.zig").parse;
12pub const parseStringLiteral = @import("zig/string_literal.zig").parse;12pub const parseStringLiteral = @import("zig/string_literal.zig").parse;
13pub const render = @import("zig/render.zig").render;13pub const render = @import("zig/render.zig").render;
14pub const renderStringLiteral = @import("zig/string_literal.zig").render;
15pub const ast = @import("zig/ast.zig");14pub const ast = @import("zig/ast.zig");
16pub const system = @import("zig/system.zig");15pub const system = @import("zig/system.zig");
17pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;16pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;
lib/std/zig/string_literal.zig-30
...@@ -127,33 +127,3 @@ test "parse" {...@@ -127,33 +127,3 @@ test "parse" {
127 expect(eql(u8, "foo", try parse(alloc, "\"f\x6f\x6f\"", &bad_index)));127 expect(eql(u8, "foo", try parse(alloc, "\"f\x6f\x6f\"", &bad_index)));
128 expect(eql(u8, "f💯", try parse(alloc, "\"f\u{1f4af}\"", &bad_index)));128 expect(eql(u8, "f💯", try parse(alloc, "\"f\u{1f4af}\"", &bad_index)));
129}129}
130
131/// Writes a Zig-syntax escaped string literal to the stream. Includes the double quotes.
132pub fn render(utf8: []const u8, out_stream: anytype) !void {
133 try out_stream.writeByte('"');
134 for (utf8) |byte| switch (byte) {
135 '\n' => try out_stream.writeAll("\\n"),
136 '\r' => try out_stream.writeAll("\\r"),
137 '\t' => try out_stream.writeAll("\\t"),
138 '\\' => try out_stream.writeAll("\\\\"),
139 '"' => try out_stream.writeAll("\\\""),
140 ' ', '!', '#'...'[', ']'...'~' => try out_stream.writeByte(byte),
141 else => try out_stream.print("\\x{x:0>2}", .{byte}),
142 };
143 try out_stream.writeByte('"');
144}
145
146test "render" {
147 const expect = std.testing.expect;
148 const eql = std.mem.eql;
149
150 var fixed_buf_mem: [32]u8 = undefined;
151
152 {
153 var fbs = std.io.fixedBufferStream(&fixed_buf_mem);
154 try render(" \\ hi \x07 \x11 \" derp", fbs.outStream());
155 expect(eql(u8,
156 \\" \\ hi \x07 \x11 \" derp"
157 , fbs.getWritten()));
158 }
159}
src/translate_c.zig+3-63
...@@ -1972,16 +1972,7 @@ fn transStringLiteral(...@@ -1972,16 +1972,7 @@ fn transStringLiteral(
1972 const bytes_ptr = stmt.getString_bytes_begin_size(&len);1972 const bytes_ptr = stmt.getString_bytes_begin_size(&len);
1973 const str = bytes_ptr[0..len];1973 const str = bytes_ptr[0..len];
19741974
1975 var char_buf: [4]u8 = undefined;1975 const token = try appendTokenFmt(rp.c, .StringLiteral, "\"{Z}\"", .{str});
1976 len = 0;
1977 for (str) |c| len += escapeChar(c, &char_buf).len;
1978
1979 const buf = try rp.c.arena.alloc(u8, len + "\"\"".len);
1980 buf[0] = '"';
1981 writeEscapedString(buf[1..], str);
1982 buf[buf.len - 1] = '"';
1983
1984 const token = try appendToken(rp.c, .StringLiteral, buf);
1985 const node = try rp.c.arena.create(ast.Node.OneToken);1976 const node = try rp.c.arena.create(ast.Node.OneToken);
1986 node.* = .{1977 node.* = .{
1987 .base = .{ .tag = .StringLiteral },1978 .base = .{ .tag = .StringLiteral },
...@@ -1999,41 +1990,6 @@ fn transStringLiteral(...@@ -1999,41 +1990,6 @@ fn transStringLiteral(
1999 }1990 }
2000}1991}
20011992
2002fn escapedStringLen(s: []const u8) usize {
2003 var len: usize = 0;
2004 var char_buf: [4]u8 = undefined;
2005 for (s) |c| len += escapeChar(c, &char_buf).len;
2006 return len;
2007}
2008
2009fn writeEscapedString(buf: []u8, s: []const u8) void {
2010 var char_buf: [4]u8 = undefined;
2011 var i: usize = 0;
2012 for (s) |c| {
2013 const escaped = escapeChar(c, &char_buf);
2014 mem.copy(u8, buf[i..], escaped);
2015 i += escaped.len;
2016 }
2017}
2018
2019// Returns either a string literal or a slice of `buf`.
2020fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 {
2021 return switch (c) {
2022 '\"' => "\\\"",
2023 '\'' => "\\'",
2024 '\\' => "\\\\",
2025 '\n' => "\\n",
2026 '\r' => "\\r",
2027 '\t' => "\\t",
2028 // Handle the remaining escapes Zig doesn't support by turning them
2029 // into their respective hex representation
2030 else => if (std.ascii.isCntrl(c))
2031 std.fmt.bufPrint(char_buf, "\\x{x:0>2}", .{c}) catch unreachable
2032 else
2033 std.fmt.bufPrint(char_buf, "{c}", .{c}) catch unreachable,
2034 };
2035}
2036
2037fn transCCast(1993fn transCCast(
2038 rp: RestorePoint,1994 rp: RestorePoint,
2039 scope: *Scope,1995 scope: *Scope,
...@@ -2922,8 +2878,7 @@ fn transCharLiteral(...@@ -2922,8 +2878,7 @@ fn transCharLiteral(
2922 if (val > 255)2878 if (val > 255)
2923 break :blk try transCreateNodeInt(rp.c, val);2879 break :blk try transCreateNodeInt(rp.c, val);
2924 }2880 }
2925 var char_buf: [4]u8 = undefined;2881 const token = try appendTokenFmt(rp.c, .CharLiteral, "'{Z}'", .{@intCast(u8, val)});
2926 const token = try appendTokenFmt(rp.c, .CharLiteral, "'{}'", .{escapeChar(@intCast(u8, val), &char_buf)});
2927 const node = try rp.c.arena.create(ast.Node.OneToken);2882 const node = try rp.c.arena.create(ast.Node.OneToken);
2928 node.* = .{2883 node.* = .{
2929 .base = .{ .tag = .CharLiteral },2884 .base = .{ .tag = .CharLiteral },
...@@ -5247,23 +5202,8 @@ fn isZigPrimitiveType(name: []const u8) bool {...@@ -5247,23 +5202,8 @@ fn isZigPrimitiveType(name: []const u8) bool {
5247 mem.eql(u8, name, "c_ulonglong");5202 mem.eql(u8, name, "c_ulonglong");
5248}5203}
52495204
5250fn isValidZigIdentifier(name: []const u8) bool {
5251 for (name) |c, i| {
5252 switch (c) {
5253 '_', 'a'...'z', 'A'...'Z' => {},
5254 '0'...'9' => if (i == 0) return false,
5255 else => return false,
5256 }
5257 }
5258 return true;
5259}
5260
5261fn appendIdentifier(c: *Context, name: []const u8) !ast.TokenIndex {5205fn appendIdentifier(c: *Context, name: []const u8) !ast.TokenIndex {
5262 if (!isValidZigIdentifier(name) or std.zig.Token.getKeyword(name) != null) {5206 return appendTokenFmt(c, .Identifier, "{z}", .{name});
5263 return appendTokenFmt(c, .Identifier, "@\"{}\"", .{name});
5264 } else {
5265 return appendTokenFmt(c, .Identifier, "{}", .{name});
5266 }
5267}5207}
52685208
5269fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node {5209fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node {
src/value.zig+2-1
...@@ -350,7 +350,8 @@ pub const Value = extern union {...@@ -350,7 +350,8 @@ pub const Value = extern union {
350 val = elem_ptr.array_ptr;350 val = elem_ptr.array_ptr;
351 },351 },
352 .empty_array => return out_stream.writeAll(".{}"),352 .empty_array => return out_stream.writeAll(".{}"),
353 .enum_literal, .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),353 .enum_literal => return out_stream.print(".{z}", .{self.cast(Payload.Bytes).?.data}),
354 .bytes => return out_stream.print("\"{Z}\"", .{self.cast(Payload.Bytes).?.data}),
354 .repeated => {355 .repeated => {
355 try out_stream.writeAll("(repeated) ");356 try out_stream.writeAll("(repeated) ");
356 val = val.cast(Payload.Repeated).?.val;357 val = val.cast(Payload.Repeated).?.val;
src/zir.zig+4-4
...@@ -1216,17 +1216,17 @@ const Writer = struct {...@@ -1216,17 +1216,17 @@ const Writer = struct {
1216 try stream.writeByte('}');1216 try stream.writeByte('}');
1217 },1217 },
1218 bool => return stream.writeByte("01"[@boolToInt(param)]),1218 bool => return stream.writeByte("01"[@boolToInt(param)]),
1219 []u8, []const u8 => return std.zig.renderStringLiteral(param, stream),1219 []u8, []const u8 => return stream.print("\"{Z}\"", .{param}),
1220 BigIntConst, usize => return stream.print("{}", .{param}),1220 BigIntConst, usize => return stream.print("{}", .{param}),
1221 TypedValue => unreachable, // this is a special case1221 TypedValue => unreachable, // this is a special case
1222 *IrModule.Decl => unreachable, // this is a special case1222 *IrModule.Decl => unreachable, // this is a special case
1223 *Inst.Block => {1223 *Inst.Block => {
1224 const name = self.block_table.get(param).?;1224 const name = self.block_table.get(param).?;
1225 return std.zig.renderStringLiteral(name, stream);1225 return stream.print("\"{Z}\"", .{name});
1226 },1226 },
1227 *Inst.Loop => {1227 *Inst.Loop => {
1228 const name = self.loop_table.get(param).?;1228 const name = self.loop_table.get(param).?;
1229 return std.zig.renderStringLiteral(name, stream);1229 return stream.print("\"{Z}\"", .{name});
1230 },1230 },
1231 [][]const u8 => {1231 [][]const u8 => {
1232 try stream.writeByte('[');1232 try stream.writeByte('[');
...@@ -1234,7 +1234,7 @@ const Writer = struct {...@@ -1234,7 +1234,7 @@ const Writer = struct {
1234 if (i != 0) {1234 if (i != 0) {
1235 try stream.writeAll(", ");1235 try stream.writeAll(", ");
1236 }1236 }
1237 try std.zig.renderStringLiteral(str, stream);1237 try stream.print("\"{Z}\"", .{str});
1238 }1238 }
1239 try stream.writeByte(']');1239 try stream.writeByte(']');
1240 },1240 },