From 31802c6c68a98bdbe34766d3cfdaf65b782851da Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sun, 3 Jan 2021 13:49:51 -0700 Subject: [PATCH] remove z/Z format specifiers Zig's format system is flexible enough to add custom formatters. This PR removes the new z/Z format specifiers that were added for printing Zig identifiers and replaces them with custom formatters. --- lib/std/build.zig | 32 ++++++++-------- lib/std/fmt.zig | 89 +++++++++++++++------------------------------ lib/std/fs/wasi.zig | 2 +- lib/std/zig.zig | 2 + lib/std/zig/fmt.zig | 71 ++++++++++++++++++++++++++++++++++++ src/Compilation.zig | 44 +++++++++++----------- src/codegen/c.zig | 4 +- src/translate_c.zig | 7 ++-- src/value.zig | 4 +- src/zir.zig | 8 ++-- 10 files changed, 153 insertions(+), 110 deletions(-) create mode 100644 lib/std/zig/fmt.zig diff --git a/lib/std/build.zig b/lib/std/build.zig index cb4cb229e3..5fd64cad0b 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1852,25 +1852,25 @@ pub const LibExeObjStep = struct { const out = self.build_options_contents.writer(); switch (T) { []const []const u8 => { - out.print("pub const {z}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable; + out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable; for (value) |slice| { - out.print(" \"{Z}\",\n", .{slice}) catch unreachable; + out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable; } out.writeAll("};\n") catch unreachable; return; }, [:0]const u8 => { - out.print("pub const {z}: [:0]const u8 = \"{Z}\";\n", .{ name, value }) catch unreachable; + out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; return; }, []const u8 => { - out.print("pub const {z}: []const u8 = \"{Z}\";\n", .{ name, value }) catch unreachable; + out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; return; }, ?[]const u8 => { - out.print("pub const {z}: ?[]const u8 = ", .{name}) catch unreachable; + out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; if (value) |payload| { - out.print("\"{Z}\";\n", .{payload}) catch unreachable; + out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; } else { out.writeAll("null;\n") catch unreachable; } @@ -1878,14 +1878,14 @@ pub const LibExeObjStep = struct { }, std.builtin.Version => { out.print( - \\pub const {z}: @import("builtin").Version = .{{ + \\pub const {}: @import("builtin").Version = .{{ \\ .major = {d}, \\ .minor = {d}, \\ .patch = {d}, \\}}; \\ , .{ - name, + std.zig.fmtId(name), value.major, value.minor, @@ -1894,23 +1894,23 @@ pub const LibExeObjStep = struct { }, std.SemanticVersion => { out.print( - \\pub const {z}: @import("std").SemanticVersion = .{{ + \\pub const {}: @import("std").SemanticVersion = .{{ \\ .major = {d}, \\ .minor = {d}, \\ .patch = {d}, \\ , .{ - name, + std.zig.fmtId(name), value.major, value.minor, value.patch, }) catch unreachable; if (value.pre) |some| { - out.print(" .pre = \"{Z}\",\n", .{some}) catch unreachable; + out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; } if (value.build) |some| { - out.print(" .build = \"{Z}\",\n", .{some}) catch unreachable; + out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; } out.writeAll("};\n") catch unreachable; return; @@ -1919,15 +1919,15 @@ pub const LibExeObjStep = struct { } switch (@typeInfo(T)) { .Enum => |enum_info| { - out.print("pub const {z} = enum {{\n", .{@typeName(T)}) catch unreachable; + out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable; inline for (enum_info.fields) |field| { - out.print(" {z},\n", .{field.name}) catch unreachable; + out.print(" {},\n", .{std.zig.fmtId(field.name)}) catch unreachable; } out.writeAll("};\n") catch unreachable; }, else => {}, } - out.print("pub const {z}: {s} = {};\n", .{ name, @typeName(T), value }) catch unreachable; + out.print("pub const {}: {s} = {};\n", .{ std.zig.fmtId(name), @typeName(T), value }) catch unreachable; } /// The value is the path in the cache dir. @@ -2157,7 +2157,7 @@ pub const LibExeObjStep = struct { // Render build artifact options at the last minute, now that the path is known. for (self.build_options_artifact_args.items) |item| { const out = self.build_options_contents.writer(); - out.print("pub const {s}: []const u8 = \"{Z}\";\n", .{ item.name, item.artifact.getOutputPath() }) catch unreachable; + out.print("pub const {s}: []const u8 = \"{}\";\n", .{ item.name, std.zig.fmtEscapes(item.artifact.getOutputPath()) }) catch unreachable; } const build_options_file = try fs.path.join( diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index bc9ac92283..d8b81eb264 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -715,9 +715,9 @@ pub fn formatText( } return; } else if (comptime std.mem.eql(u8, fmt, "z")) { - return formatZigIdentifier(bytes, options, writer); + @compileError("specifier 'z' has been deprecated, wrap your argument in std.zig.fmtId instead"); } else if (comptime std.mem.eql(u8, fmt, "Z")) { - return formatZigEscapes(bytes, options, writer); + @compileError("specifier 'Z' has been deprecated, wrap your argument in std.zig.fmtEscapes instead"); } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } @@ -782,52 +782,6 @@ pub fn formatBuf( } } -/// Print the string as a Zig identifier escaping it with @"" syntax if needed. -pub fn formatZigIdentifier( - bytes: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - if (isValidZigIdentifier(bytes)) { - return writer.writeAll(bytes); - } - try writer.writeAll("@\""); - try formatZigEscapes(bytes, options, writer); - try writer.writeByte('"'); -} - -fn isValidZigIdentifier(bytes: []const u8) bool { - for (bytes) |c, i| { - switch (c) { - '_', 'a'...'z', 'A'...'Z' => {}, - '0'...'9' => if (i == 0) return false, - else => return false, - } - } - return std.zig.Token.getKeyword(bytes) == null; -} - -pub fn formatZigEscapes( - bytes: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - for (bytes) |byte| switch (byte) { - '\n' => try writer.writeAll("\\n"), - '\r' => try writer.writeAll("\\r"), - '\t' => try writer.writeAll("\\t"), - '\\' => try writer.writeAll("\\\\"), - '"' => try writer.writeAll("\\\""), - '\'' => try writer.writeAll("\\'"), - ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte), - // Use hex escapes for rest any unprintable characters. - else => { - try writer.writeAll("\\x"); - try formatInt(byte, 16, false, .{ .width = 2, .fill = '0' }, writer); - }, - }; -} - /// Print a float in scientific notation to the specified precision. Null uses full precision. /// It should be the case that every full precision, printed value can be re-parsed back to the /// same type unambiguously. @@ -1173,6 +1127,32 @@ pub const ParseIntError = error{ InvalidCharacter, }; +/// Creates a Formatter type from a format function. Wrapping data in Formatter(func) causes +/// the data to be formatted using the given function `func`. `func` must be of the following +/// form: +/// +/// fn formatExample( +/// data: T, +/// comptime fmt: []const u8, +/// options: std.fmt.FormatOptions, +/// writer: anytype, +/// ) !void; +/// +pub fn Formatter(comptime format_fn: anytype) type { + const Data = @typeInfo(@TypeOf(format_fn)).Fn.args[0].arg_type.?; + return struct { + data: Data, + pub fn format( + self: @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + try format_fn(self.data, fmt, options, writer); + } + }; +} + /// Parses the string `buf` as signed or unsigned representation in the /// specified radix of an integral value of type `T`. /// @@ -1608,17 +1588,6 @@ test "escape non-printable" { try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"}); } -test "escape invalid identifiers" { - try testFmt("@\"while\"", "{z}", .{"while"}); - try testFmt("hello", "{z}", .{"hello"}); - try testFmt("@\"11\\\"23\"", "{z}", .{"11\"23"}); - try testFmt("@\"11\\x0f23\"", "{z}", .{"11\x0F23"}); - try testFmt("\\x0f", "{Z}", .{0x0f}); - try testFmt( - \\" \\ hi \x07 \x11 \" derp \'" - , "\"{Z}\"", .{" \\ hi \x07 \x11 \" derp '"}); -} - test "pointer" { { const value = @intToPtr(*align(1) i32, 0xdeadbeef); @@ -1898,7 +1867,7 @@ test "bytes.hex" { try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); } -fn testFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { +pub fn testFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { var buf: [100]u8 = undefined; const result = try bufPrint(buf[0..], template, args); if (mem.eql(u8, result, expected)) return; diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index 900adc5e2d..cf5431f994 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -38,7 +38,7 @@ pub const PreopenType = union(PreopenTypeTag) { pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void { try out_stream.print("PreopenType{{ ", .{}); switch (self) { - PreopenType.Dir => |path| try out_stream.print(".Dir = '{z}'", .{path}), + PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{std.zig.fmtId(path)}), } return out_stream.print(" }}", .{}); } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 5d95031e02..c39eb6b05f 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -8,6 +8,8 @@ const tokenizer = @import("zig/tokenizer.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; +pub const fmtId = @import("zig/fmt.zig").fmtId; +pub const fmtEscapes = @import("zig/fmt.zig").fmtEscapes; pub const parse = @import("zig/parse.zig").parse; pub const parseStringLiteral = @import("zig/string_literal.zig").parse; pub const render = @import("zig/render.zig").render; diff --git a/lib/std/zig/fmt.zig b/lib/std/zig/fmt.zig new file mode 100644 index 0000000000..d852f2b49d --- /dev/null +++ b/lib/std/zig/fmt.zig @@ -0,0 +1,71 @@ +const std = @import("std"); +const mem = std.mem; + +/// Print the string as a Zig identifier escaping it with @"" syntax if needed. +pub fn formatId( + bytes: []const u8, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + if (isValidId(bytes)) { + return writer.writeAll(bytes); + } + try writer.writeAll("@\""); + try formatEscapes(bytes, fmt, options, writer); + try writer.writeByte('"'); +} + +/// Return a Formatter for a Zig identifier +pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) { + return .{ .data = bytes }; +} + +pub fn isValidId(bytes: []const u8) bool { + for (bytes) |c, i| { + switch (c) { + '_', 'a'...'z', 'A'...'Z' => {}, + '0'...'9' => if (i == 0) return false, + else => return false, + } + } + return std.zig.Token.getKeyword(bytes) == null; +} + +pub fn formatEscapes( + bytes: []const u8, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + for (bytes) |byte| switch (byte) { + '\n' => try writer.writeAll("\\n"), + '\r' => try writer.writeAll("\\r"), + '\t' => try writer.writeAll("\\t"), + '\\' => try writer.writeAll("\\\\"), + '"' => try writer.writeAll("\\\""), + '\'' => try writer.writeAll("\\'"), + ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte), + // Use hex escapes for rest any unprintable characters. + else => { + try writer.writeAll("\\x"); + try std.fmt.formatInt(byte, 16, false, .{ .width = 2, .fill = '0' }, writer); + }, + }; +} + +/// Return a Formatter for Zig Escapes +pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(formatEscapes) { + return .{ .data = bytes }; +} + +test "escape invalid identifiers" { + try std.fmt.testFmt("@\"while\"", "{}", .{fmtId("while")}); + try std.fmt.testFmt("hello", "{}", .{fmtId("hello")}); + try std.fmt.testFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")}); + try std.fmt.testFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")}); + try std.fmt.testFmt("\\x0f", "{}", .{fmtEscapes("\x0f")}); + try std.fmt.testFmt( + \\" \\ hi \x07 \x11 \" derp \'" + , "\"{}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")}); +} diff --git a/src/Compilation.zig b/src/Compilation.zig index a81926bf19..75b9ac7520 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2703,27 +2703,27 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const arch = Target.current.cpu.arch; \\/// Deprecated \\pub const endian = Target.current.cpu.arch.endian(); - \\pub const output_mode = OutputMode.{z}; - \\pub const link_mode = LinkMode.{z}; + \\pub const output_mode = OutputMode.{}; + \\pub const link_mode = LinkMode.{}; \\pub const is_test = {}; \\pub const single_threaded = {}; - \\pub const abi = Abi.{z}; + \\pub const abi = Abi.{}; \\pub const cpu: Cpu = Cpu{{ - \\ .arch = .{z}, - \\ .model = &Target.{z}.cpu.{z}, - \\ .features = Target.{z}.featureSet(&[_]Target.{z}.Feature{{ + \\ .arch = .{}, + \\ .model = &Target.{}.cpu.{}, + \\ .features = Target.{}.featureSet(&[_]Target.{}.Feature{{ \\ , .{ - @tagName(comp.bin_file.options.output_mode), - @tagName(comp.bin_file.options.link_mode), + std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)), + std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)), comp.bin_file.options.is_test, comp.bin_file.options.single_threaded, - @tagName(target.abi), - @tagName(target.cpu.arch), - generic_arch_name, - target.cpu.model.name, - generic_arch_name, - generic_arch_name, + std.zig.fmtId(@tagName(target.abi)), + std.zig.fmtId(@tagName(target.cpu.arch)), + std.zig.fmtId(generic_arch_name), + std.zig.fmtId(target.cpu.model.name), + std.zig.fmtId(generic_arch_name), + std.zig.fmtId(generic_arch_name), }); for (target.cpu.arch.allFeaturesList()) |feature, index_usize| { @@ -2742,10 +2742,10 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\ }}), \\}}; \\pub const os = Os{{ - \\ .tag = .{z}, + \\ .tag = .{}, \\ .version_range = .{{ , - .{@tagName(target.os.tag)}, + .{std.zig.fmtId(@tagName(target.os.tag))}, ); switch (target.os.getVersionRange()) { @@ -2828,8 +2828,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 (comp.bin_file.options.skip_linker_dependencies and comp.bin_file.options.parent_compilation_link_libc); try buffer.writer().print( - \\pub const object_format = ObjectFormat.{z}; - \\pub const mode = Mode.{z}; + \\pub const object_format = ObjectFormat.{}; + \\pub const mode = Mode.{}; \\pub const link_libc = {}; \\pub const link_libcpp = {}; \\pub const have_error_return_tracing = {}; @@ -2837,11 +2837,11 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const position_independent_code = {}; \\pub const position_independent_executable = {}; \\pub const strip_debug_info = {}; - \\pub const code_model = CodeModel.{z}; + \\pub const code_model = CodeModel.{}; \\ , .{ - @tagName(comp.bin_file.options.object_format), - @tagName(comp.bin_file.options.optimize_mode), + std.zig.fmtId(@tagName(comp.bin_file.options.object_format)), + std.zig.fmtId(@tagName(comp.bin_file.options.optimize_mode)), link_libc, comp.bin_file.options.link_libcpp, comp.bin_file.options.error_return_tracing, @@ -2849,7 +2849,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 comp.bin_file.options.pic, comp.bin_file.options.pie, comp.bin_file.options.strip, - @tagName(comp.bin_file.options.machine_code_model), + std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)), }); if (comp.bin_file.options.is_test) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 3de79e2a3d..8c85f482fd 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -169,8 +169,8 @@ pub const DeclGen = struct { .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), .bytes => { const bytes = val.castTag(.bytes).?.data; - // TODO: make our own C string escape instead of using {Z} - try writer.print("\"{Z}\"", .{bytes}); + // TODO: make our own C string escape instead of using std.zig.fmtEscapes + try writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)}); }, else => { // Fall back to generic implementation. diff --git a/src/translate_c.zig b/src/translate_c.zig index 8df7963267..baca8c12de 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2031,7 +2031,7 @@ fn transStringLiteral( const bytes_ptr = stmt.getString_bytes_begin_size(&len); const str = bytes_ptr[0..len]; - const token = try appendTokenFmt(rp.c, .StringLiteral, "\"{Z}\"", .{str}); + const token = try appendTokenFmt(rp.c, .StringLiteral, "\"{}\"", .{std.zig.fmtEscapes(str)}); const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .StringLiteral }, @@ -2944,7 +2944,8 @@ fn transCharLiteral( if (val > 255) break :blk try transCreateNodeInt(rp.c, val); } - const token = try appendTokenFmt(rp.c, .CharLiteral, "'{Z}'", .{@intCast(u8, val)}); + const val_array = [_]u8 { @intCast(u8, val) }; + const token = try appendTokenFmt(rp.c, .CharLiteral, "'{}'", .{std.zig.fmtEscapes(&val_array)}); const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .CharLiteral }, @@ -5315,7 +5316,7 @@ fn isZigPrimitiveType(name: []const u8) bool { } fn appendIdentifier(c: *Context, name: []const u8) !ast.TokenIndex { - return appendTokenFmt(c, .Identifier, "{z}", .{name}); + return appendTokenFmt(c, .Identifier, "{}", .{std.zig.fmtId(name)}); } fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node { diff --git a/src/value.zig b/src/value.zig index 11c385b446..036e3b71bf 100644 --- a/src/value.zig +++ b/src/value.zig @@ -491,8 +491,8 @@ pub const Value = extern union { val = elem_ptr.array_ptr; }, .empty_array => return out_stream.writeAll(".{}"), - .enum_literal => return out_stream.print(".{z}", .{self.castTag(.enum_literal).?.data}), - .bytes => return out_stream.print("\"{Z}\"", .{self.castTag(.bytes).?.data}), + .enum_literal => return out_stream.print(".{}", .{std.zig.fmtId(self.castTag(.enum_literal).?.data)}), + .bytes => return out_stream.print("\"{}\"", .{std.zig.fmtEscapes(self.castTag(.bytes).?.data)}), .repeated => { try out_stream.writeAll("(repeated) "); val = val.castTag(.repeated).?.data; diff --git a/src/zir.zig b/src/zir.zig index b3d004f1dc..246e9dda1f 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1308,17 +1308,17 @@ const Writer = struct { try stream.writeByte('}'); }, bool => return stream.writeByte("01"[@boolToInt(param)]), - []u8, []const u8 => return stream.print("\"{Z}\"", .{param}), + []u8, []const u8 => return stream.print("\"{}\"", .{std.zig.fmtEscapes(param)}), BigIntConst, usize => return stream.print("{}", .{param}), TypedValue => return stream.print("TypedValue{{ .ty = {}, .val = {}}}", .{ param.ty, param.val }), *IrModule.Decl => return stream.print("Decl({s})", .{param.name}), *Inst.Block => { const name = self.block_table.get(param).?; - return stream.print("\"{Z}\"", .{name}); + return stream.print("\"{}\"", .{std.zig.fmtEscapes(name)}); }, *Inst.Loop => { const name = self.loop_table.get(param).?; - return stream.print("\"{Z}\"", .{name}); + return stream.print("\"{}\"", .{std.zig.fmtEscapes(name)}); }, [][]const u8 => { try stream.writeByte('['); @@ -1326,7 +1326,7 @@ const Writer = struct { if (i != 0) { try stream.writeAll(", "); } - try stream.print("\"{Z}\"", .{str}); + try stream.print("\"{}\"", .{std.zig.fmtEscapes(str)}); } try stream.writeByte(']'); },