Commit 8d38a91ca8

Vexu <git@vexu.eu>
2020-09-22 14:15:41
std.fmt: add specifier for Zig identifiers
1 parent 245d98d
Changed files (1)
lib
lib/std/fmt.zig
@@ -65,6 +65,8 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
 ///   - format the non-numeric value as a string of bytes in hexadecimal notation ("binary dump") in either lower case or upper case
 ///   - output numeric value in hexadecimal notation
 /// - `s`: print a pointer-to-many as a c-string, use zero-termination
+/// - `z`: escape the string with @"" syntax if it is not a valid Zig identifier.
+/// - `Z`: print the string escaping non-printable characters using Zig escape sequences.
 /// - `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.
 /// - `e` and `E`: if printing a string, escape non-printable characters
 /// - `e`: output floating point value in scientific notation
@@ -543,7 +545,14 @@ pub fn formatIntValue(
         } else {
             @compileError("Cannot print integer that is larger than 8 bits as a ascii");
         }
-    } else if (comptime std.mem.eql(u8, fmt, "b")) {
+    } else if (comptime std.mem.eql(u8, fmt, "Z")) {
+        if (@typeInfo(@TypeOf(int_value)).Int.bits <= 8) {
+            const c: u8 = int_value;
+            return formatZigEscapes(@as(*const [1]u8, &c), options, writer);
+        } else {
+            @compileError("Cannot escape character with more than 8 bits");
+        }
+    }else if (comptime std.mem.eql(u8, fmt, "b")) {
         radix = 2;
         uppercase = false;
     } else if (comptime std.mem.eql(u8, fmt, "x")) {
@@ -612,6 +621,10 @@ pub fn formatText(
             }
         }
         return;
+    } else if (comptime std.mem.eql(u8, fmt, "z")) {
+        return formatZigIdentifier(bytes, options, writer);
+    } else if (comptime std.mem.eql(u8, fmt, "Z")) {
+        return formatZigEscapes(bytes, options, writer);
     } else {
         @compileError("Unknown format string: '" ++ fmt ++ "'");
     }
@@ -652,9 +665,62 @@ pub fn formatBuf(
     }
 }
 
-// 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.
+/// 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) |c| {
+        const s: []const u8 = switch (c) {
+            '\"' => "\\\"",
+            '\'' => "\\'",
+            '\\' => "\\\\",
+            '\n' => "\\n",
+            '\r' => "\\r",
+            '\t' => "\\t",
+            // Handle the remaining escapes Zig doesn't support by turning them
+            // into their respective hex representation
+            else => if (std.ascii.isCntrl(c)) {
+                try writer.writeAll("\\x");
+                try formatInt(c, 16, false, .{ .width = 2, .fill = '0' }, writer);
+                continue;
+            } else {
+                try writer.writeByte(c);
+                continue;
+            },
+        };
+        try writer.writeAll(s);
+    }
+}
+
+/// 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.
 pub fn formatFloatScientific(
     value: anytype,
     options: FormatOptions,
@@ -746,8 +812,8 @@ pub fn formatFloatScientific(
     }
 }
 
-// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
-// By default floats are printed at full precision (no rounding).
+/// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
+/// By default floats are printed at full precision (no rounding).
 pub fn formatFloatDecimal(
     value: anytype,
     options: FormatOptions,
@@ -1136,7 +1202,7 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr
     return result[0 .. result.len - 1 :0];
 }
 
-// Count the characters needed for format. Useful for preallocating memory
+/// Count the characters needed for format. Useful for preallocating memory
 pub fn count(comptime fmt: []const u8, args: anytype) u64 {
     var counting_writer = std.io.countingWriter(std.io.null_writer);
     format(counting_writer.writer(), fmt, args) catch |err| switch (err) {};
@@ -1334,6 +1400,14 @@ 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});
+}
+
 test "pointer" {
     {
         const value = @intToPtr(*align(1) i32, 0xdeadbeef);