authorgravatar for eric.saintetienne@gmail.comeric-saintetienne <eric.saintetienne@gmail.com> 2024-07-17 20:02:10+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-07-17 19:02:10+00:00
logc3faae6bf1589380e983aaf9e3238f053ba4a674
tree4f6fe3d65daa3aa81cf08f150e26aadd12ac01b0
parent9d9b5a11e873cc15e3f1b6e506ecf22c8380c87d
signaturebadge-check Signed by PGP key B5690EEEBB952194

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

1 files changed, 43 insertions(+), 22 deletions(-)

lib/std/fmt.zig+43-22
......@@ -20,11 +20,14 @@ pub const Alignment = enum {
2020 right,
2121};
2222
23const default_alignment = .right;
24const default_fill_char = ' ';
25
2326pub const FormatOptions = struct {
2427 precision: ?usize = null,
2528 width: ?usize = null,
26 alignment: Alignment = .right,
27 fill: u21 = ' ',
29 alignment: Alignment = default_alignment,
30 fill: u21 = default_fill_char,
2831};
2932
3033/// Renders fmt string with args, calling `writer` with slices of bytes.
......@@ -48,8 +51,8 @@ pub const FormatOptions = struct {
4851///
4952/// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when
5053/// all parameters after the separator are omitted.
51/// Only exception is the *fill* parameter. If *fill* is required, one has to specify *alignment* as well, as otherwise
52/// the digits after `:` is interpreted as *width*, not *fill*.
54/// Only exception is the *fill* parameter. If a non-zero *fill* character is required at the same time as *width* is specified,
55/// one has to specify *alignment* as well, as otherwise the digit following `:` is interpreted as *width*, not *fill*.
5356///
5457/// The *specifier* has several options for types:
5558/// - `x` and `X`: output numeric value in hexadecimal notation
......@@ -239,29 +242,38 @@ pub const Placeholder = struct {
239242 }
240243 }
241244
242 // Parse the fill character
243 // The fill parameter requires the alignment parameter to be specified
244 // too
245 const fill = comptime if (parser.peek(1)) |ch|
245 // Parse the fill character, if present.
246 // When the width field is also specified, the fill character must
247 // be followed by an alignment specifier, unless it's '0' (zero)
248 // (in which case it's handled as part of the width specifier)
249 var fill: ?u21 = comptime if (parser.peek(1)) |ch|
246250 switch (ch) {
247 '<', '^', '>' => parser.char().?,
248 else => ' ',
251 '<', '^', '>' => parser.char(),
252 else => null,
249253 }
250254 else
251 ' ';
255 null;
252256
253257 // Parse the alignment parameter
254 const alignment: Alignment = comptime if (parser.peek(0)) |ch| init: {
258 const alignment: ?Alignment = comptime if (parser.peek(0)) |ch| init: {
255259 switch (ch) {
256 '<', '^', '>' => _ = parser.char(),
257 else => {},
260 '<', '^', '>' => {
261 // consume the character
262 break :init switch (parser.char().?) {
263 '<' => .left,
264 '^' => .center,
265 else => .right,
266 };
267 },
268 else => break :init null,
258269 }
259 break :init switch (ch) {
260 '<' => .left,
261 '^' => .center,
262 else => .right,
263 };
264 } else .right;
270 } else null;
271
272 // When none of the fill character and the alignment specifier have
273 // been provided, check whether the width starts with a zero.
274 if (fill == null and alignment == null) {
275 fill = comptime if (parser.peek(0) == '0') '0' else null;
276 }
265277
266278 // Parse the width parameter
267279 const width = comptime parser.specifier() catch |err|
......@@ -284,8 +296,8 @@ pub const Placeholder = struct {
284296
285297 return Placeholder{
286298 .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*),
287 .fill = fill,
288 .alignment = alignment,
299 .fill = fill orelse default_fill_char,
300 .alignment = alignment orelse default_alignment,
289301 .arg = arg,
290302 .width = width,
291303 .precision = precision,
......@@ -2648,6 +2660,15 @@ test "sci float padding" {
26482660 try expectFmt("right-pad: 3.142e0****\n", "right-pad: {e:*<11.3}\n", .{number});
26492661}
26502662
2663test "padding.zero" {
2664 try expectFmt("zero-pad: '0042'", "zero-pad: '{:04}'", .{42});
2665 try expectFmt("std-pad: ' 42'", "std-pad: '{:10}'", .{42});
2666 try expectFmt("std-pad-1: '001'", "std-pad-1: '{:0>3}'", .{1});
2667 try expectFmt("std-pad-2: '911'", "std-pad-2: '{:1<03}'", .{9});
2668 try expectFmt("std-pad-3: ' 1'", "std-pad-3: '{:>03}'", .{1});
2669 try expectFmt("center-pad: '515'", "center-pad: '{:5^03}'", .{1});
2670}
2671
26512672test "null" {
26522673 const inst = null;
26532674 try expectFmt("null", "{}", .{inst});