| ... | ... | @@ -65,6 +65,8 @@ fn peekIsAlign(comptime fmt: []const u8) bool { |
| 65 | 65 | /// - format the non-numeric value as a string of bytes in hexadecimal notation ("binary dump") in either lower case or upper case |
| 66 | 66 | /// - output numeric value in hexadecimal notation |
| 67 | 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 | 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 | 71 | /// - `e` and `E`: if printing a string, escape non-printable characters |
| 70 | 72 | /// - `e`: output floating point value in scientific notation |
| ... | ... | @@ -543,7 +545,14 @@ pub fn formatIntValue( |
| 543 | 545 | } else { |
| 544 | 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 | 556 | radix = 2; |
| 548 | 557 | uppercase = false; |
| 549 | 558 | } else if (comptime std.mem.eql(u8, fmt, "x")) { |
| ... | ... | @@ -612,6 +621,10 @@ pub fn formatText( |
| 612 | 621 | } |
| 613 | 622 | } |
| 614 | 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 | 628 | } else { |
| 616 | 629 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| 617 | 630 | } |
| ... | ... | @@ -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. |
| 656 | | // It should be the case that every full precision, printed value can be re-parsed back to the |
| 657 | | // same type unambiguously. |
| 668 | /// Print the string as a Zig identifier escaping it with @"" syntax if needed. |
| 669 | pub fn formatZigIdentifier( |
| 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 | 724 | pub fn formatFloatScientific( |
| 659 | 725 | value: anytype, |
| 660 | 726 | options: FormatOptions, |
| ... | ... | @@ -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. |
| 750 | | // By default floats are printed at full precision (no rounding). |
| 815 | /// Print a float of the format x.yyyyy where the number of y is specified by the precision argument. |
| 816 | /// By default floats are printed at full precision (no rounding). |
| 751 | 817 | pub fn formatFloatDecimal( |
| 752 | 818 | value: anytype, |
| 753 | 819 | options: FormatOptions, |
| ... | ... | @@ -1136,7 +1202,7 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr |
| 1136 | 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 | 1206 | pub fn count(comptime fmt: []const u8, args: anytype) u64 { |
| 1141 | 1207 | var counting_writer = std.io.countingWriter(std.io.null_writer); |
| 1142 | 1208 | format(counting_writer.writer(), fmt, args) catch |err| switch (err) {}; |
| ... | ... | @@ -1334,6 +1400,14 @@ test "escape non-printable" { |
| 1334 | 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 | 1411 | test "pointer" { |
| 1338 | 1412 | { |
| 1339 | 1413 | const value = @intToPtr(*align(1) i32, 0xdeadbeef); |