From c3faae6bf1589380e983aaf9e3238f053ba4a674 Mon Sep 17 00:00:00 2001 From: eric-saintetienne Date: Wed, 17 Jul 2024 20:02:10 +0100 Subject: [PATCH] format: do not force user to provide an alignment field when it's not necessary (#19049) * format: fix default character when no alignment When no alignment is specified, the character that should be used is the fill character that is otherwise provided, not space. This is closer to the default that C programmers (and other languages) use: "04x" fills with zeroes (in zig as of today x:04 fills with spaces) Test: const std = @import("std"); const expectFmt = std.testing.expectFmt; test "fmt.defaultchar.no-alignment" { // as of today the following test passes: try expectFmt("0x00ff", "0x{x:0>4}", .{255}); // as of today the following test fails (returns "0x ff" instead) try expectFmt("0x00ff", "0x{x:04}", .{255}); } * non breaking improvement of string formatting * improved comment * simplify the code a little * small improvement around how characters identified as valid are consumed --- lib/std/fmt.zig | 65 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 1a7475e1c5..a4407a8cd2 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -20,11 +20,14 @@ pub const Alignment = enum { right, }; +const default_alignment = .right; +const default_fill_char = ' '; + pub const FormatOptions = struct { precision: ?usize = null, width: ?usize = null, - alignment: Alignment = .right, - fill: u21 = ' ', + alignment: Alignment = default_alignment, + fill: u21 = default_fill_char, }; /// Renders fmt string with args, calling `writer` with slices of bytes. @@ -48,8 +51,8 @@ pub const FormatOptions = struct { /// /// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when /// all parameters after the separator are omitted. -/// Only exception is the *fill* parameter. If *fill* is required, one has to specify *alignment* as well, as otherwise -/// the digits after `:` is interpreted as *width*, not *fill*. +/// Only exception is the *fill* parameter. If a non-zero *fill* character is required at the same time as *width* is specified, +/// one has to specify *alignment* as well, as otherwise the digit following `:` is interpreted as *width*, not *fill*. /// /// The *specifier* has several options for types: /// - `x` and `X`: output numeric value in hexadecimal notation @@ -239,29 +242,38 @@ pub const Placeholder = struct { } } - // Parse the fill character - // The fill parameter requires the alignment parameter to be specified - // too - const fill = comptime if (parser.peek(1)) |ch| + // Parse the fill character, if present. + // When the width field is also specified, the fill character must + // be followed by an alignment specifier, unless it's '0' (zero) + // (in which case it's handled as part of the width specifier) + var fill: ?u21 = comptime if (parser.peek(1)) |ch| switch (ch) { - '<', '^', '>' => parser.char().?, - else => ' ', + '<', '^', '>' => parser.char(), + else => null, } else - ' '; + null; // Parse the alignment parameter - const alignment: Alignment = comptime if (parser.peek(0)) |ch| init: { + const alignment: ?Alignment = comptime if (parser.peek(0)) |ch| init: { switch (ch) { - '<', '^', '>' => _ = parser.char(), - else => {}, + '<', '^', '>' => { + // consume the character + break :init switch (parser.char().?) { + '<' => .left, + '^' => .center, + else => .right, + }; + }, + else => break :init null, } - break :init switch (ch) { - '<' => .left, - '^' => .center, - else => .right, - }; - } else .right; + } else null; + + // When none of the fill character and the alignment specifier have + // been provided, check whether the width starts with a zero. + if (fill == null and alignment == null) { + fill = comptime if (parser.peek(0) == '0') '0' else null; + } // Parse the width parameter const width = comptime parser.specifier() catch |err| @@ -284,8 +296,8 @@ pub const Placeholder = struct { return Placeholder{ .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*), - .fill = fill, - .alignment = alignment, + .fill = fill orelse default_fill_char, + .alignment = alignment orelse default_alignment, .arg = arg, .width = width, .precision = precision, @@ -2648,6 +2660,15 @@ test "sci float padding" { try expectFmt("right-pad: 3.142e0****\n", "right-pad: {e:*<11.3}\n", .{number}); } +test "padding.zero" { + try expectFmt("zero-pad: '0042'", "zero-pad: '{:04}'", .{42}); + try expectFmt("std-pad: ' 42'", "std-pad: '{:10}'", .{42}); + try expectFmt("std-pad-1: '001'", "std-pad-1: '{:0>3}'", .{1}); + try expectFmt("std-pad-2: '911'", "std-pad-2: '{:1<03}'", .{9}); + try expectFmt("std-pad-3: ' 1'", "std-pad-3: '{:>03}'", .{1}); + try expectFmt("center-pad: '515'", "center-pad: '{:5^03}'", .{1}); +} + test "null" { const inst = null; try expectFmt("null", "{}", .{inst});