| ... | @@ -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 case | 65 | /// - 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 notation | 66 | /// - output numeric value in hexadecimal notation |
| 67 | /// - `s`: print a pointer-to-many as a c-string, use zero-termination | 67 | /// - `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 characters | 71 | /// - `e` and `E`: if printing a string, escape non-printable characters |
| 70 | /// - `e`: output floating point value in scientific notation | 72 | /// - `e`: output floating point value in scientific notation |
| ... | @@ -543,7 +545,14 @@ pub fn formatIntValue( | ... | @@ -543,7 +545,14 @@ 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 | } |
| 546 | } else if (comptime std.mem.eql(u8, fmt, "b")) { | 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 | } |
| | 555 | }else if (comptime std.mem.eql(u8, fmt, "b")) { |
| 547 | radix = 2; | 556 | radix = 2; |
| 548 | uppercase = false; | 557 | uppercase = false; |
| 549 | } else if (comptime std.mem.eql(u8, fmt, "x")) { | 558 | } else if (comptime std.mem.eql(u8, fmt, "x")) { |
| ... | @@ -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,62 @@ pub fn formatBuf( | ... | @@ -652,9 +665,62 @@ pub fn formatBuf( |
| 652 | } | 665 | } |
| 653 | } | 666 | } |
| 654 | | 667 | |
| 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 the | 669 | pub 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 | |
| | 682 | fn 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 | |
| | 693 | pub fn formatZigEscapes( |
| | 694 | bytes: []const u8, |
| | 695 | options: FormatOptions, |
| | 696 | writer: anytype, |
| | 697 | ) !void { |
| | 698 | for (bytes) |c| { |
| | 699 | const s: []const u8 = switch (c) { |
| | 700 | '\"' => "\\\"", |
| | 701 | '\'' => "\\'", |
| | 702 | '\\' => "\\\\", |
| | 703 | '\n' => "\\n", |
| | 704 | '\r' => "\\r", |
| | 705 | '\t' => "\\t", |
| | 706 | // Handle the remaining escapes Zig doesn't support by turning them |
| | 707 | // into their respective hex representation |
| | 708 | else => if (std.ascii.isCntrl(c)) { |
| | 709 | try writer.writeAll("\\x"); |
| | 710 | try formatInt(c, 16, false, .{ .width = 2, .fill = '0' }, writer); |
| | 711 | continue; |
| | 712 | } else { |
| | 713 | try writer.writeByte(c); |
| | 714 | continue; |
| | 715 | }, |
| | 716 | }; |
| | 717 | try writer.writeAll(s); |
| | 718 | } |
| | 719 | } |
| | 720 | |
| | 721 | /// Print a float in scientific notation to the specified precision. Null uses full precision. |
| | 722 | /// It should be the case that every full precision, printed value can be re-parsed back to the |
| | 723 | /// same type unambiguously. |
| 658 | pub fn formatFloatScientific( | 724 | pub fn formatFloatScientific( |
| 659 | value: anytype, | 725 | value: anytype, |
| 660 | options: FormatOptions, | 726 | options: FormatOptions, |
| ... | @@ -746,8 +812,8 @@ pub fn formatFloatScientific( | ... | @@ -746,8 +812,8 @@ pub fn formatFloatScientific( |
| 746 | } | 812 | } |
| 747 | } | 813 | } |
| 748 | | 814 | |
| 749 | // Print a float of the format x.yyyyy where the number of y is specified by the precision argument. | 815 | /// 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). | 816 | /// By default floats are printed at full precision (no rounding). |
| 751 | pub fn formatFloatDecimal( | 817 | pub fn formatFloatDecimal( |
| 752 | value: anytype, | 818 | value: anytype, |
| 753 | options: FormatOptions, | 819 | options: FormatOptions, |
| ... | @@ -1136,7 +1202,7 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr | ... | @@ -1136,7 +1202,7 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr |
| 1136 | return result[0 .. result.len - 1 :0]; | 1202 | return result[0 .. result.len - 1 :0]; |
| 1137 | } | 1203 | } |
| 1138 | | 1204 | |
| 1139 | // Count the characters needed for format. Useful for preallocating memory | 1205 | /// Count the characters needed for format. Useful for preallocating memory |
| 1140 | pub fn count(comptime fmt: []const u8, args: anytype) u64 { | 1206 | pub fn count(comptime fmt: []const u8, args: anytype) u64 { |
| 1141 | var counting_writer = std.io.countingWriter(std.io.null_writer); | 1207 | var counting_writer = std.io.countingWriter(std.io.null_writer); |
| 1142 | format(counting_writer.writer(), fmt, args) catch |err| switch (err) {}; | 1208 | format(counting_writer.writer(), fmt, args) catch |err| switch (err) {}; |
| ... | @@ -1334,6 +1400,14 @@ test "escape non-printable" { | ... | @@ -1334,6 +1400,14 @@ test "escape non-printable" { |
| 1334 | try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"}); | 1400 | try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"}); |
| 1335 | } | 1401 | } |
| 1336 | | 1402 | |
| | 1403 | test "escape invalid identifiers" { |
| | 1404 | try testFmt("@\"while\"", "{z}", .{"while"}); |
| | 1405 | try testFmt("hello", "{z}", .{"hello"}); |
| | 1406 | try testFmt("@\"11\\\"23\"", "{z}", .{"11\"23"}); |
| | 1407 | try testFmt("@\"11\\x0f23\"", "{z}", .{"11\x0F23"}); |
| | 1408 | try testFmt("\\x0f", "{Z}", .{0x0f}); |
| | 1409 | } |
| | 1410 | |
| 1337 | test "pointer" { | 1411 | test "pointer" { |
| 1338 | { | 1412 | { |
| 1339 | const value = @intToPtr(*align(1) i32, 0xdeadbeef); | 1413 | const value = @intToPtr(*align(1) i32, 0xdeadbeef); |