Skip to content

Commit c3faae6

Browse files
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 parent 9d9b5a1 commit c3faae6

File tree

1 file changed

+43
-22
lines changed

1 file changed

+43
-22
lines changed

lib/std/fmt.zig

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ pub const Alignment = enum {
2020
right,
2121
};
2222

23+
const default_alignment = .right;
24+
const default_fill_char = ' ';
25+
2326
pub 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

2663+
test "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+
26512672
test "null" {
26522673
const inst = null;
26532674
try expectFmt("null", "{}", .{inst});

0 commit comments

Comments
 (0)