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 {...@@ -20,11 +20,14 @@ pub const Alignment = enum {
20 right,20 right,
21};21};
2222
23const default_alignment = .right;
24const default_fill_char = ' ';
25
23pub const FormatOptions = struct {26pub const FormatOptions = struct {
24 precision: ?usize = null,27 precision: ?usize = null,
25 width: ?usize = null,28 width: ?usize = null,
26 alignment: Alignment = .right,29 alignment: Alignment = default_alignment,
27 fill: u21 = ' ',30 fill: u21 = default_fill_char,
28};31};
2932
30/// Renders fmt string with args, calling `writer` with slices of bytes.33/// Renders fmt string with args, calling `writer` with slices of bytes.
...@@ -48,8 +51,8 @@ pub const FormatOptions = struct {...@@ -48,8 +51,8 @@ pub const FormatOptions = struct {
48///51///
49/// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when52/// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when
50/// all parameters after the separator are omitted.53/// 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 otherwise54/// Only exception is the *fill* parameter. If a non-zero *fill* character is required at the same time as *width* is specified,
52/// the digits after `:` is interpreted as *width*, not *fill*.55/// one has to specify *alignment* as well, as otherwise the digit following `:` is interpreted as *width*, not *fill*.
53///56///
54/// The *specifier* has several options for types:57/// The *specifier* has several options for types:
55/// - `x` and `X`: output numeric value in hexadecimal notation58/// - `x` and `X`: output numeric value in hexadecimal notation
...@@ -239,29 +242,38 @@ pub const Placeholder = struct {...@@ -239,29 +242,38 @@ pub const Placeholder = struct {
239 }242 }
240 }243 }
241244
242 // Parse the fill character245 // Parse the fill character, if present.
243 // The fill parameter requires the alignment parameter to be specified246 // When the width field is also specified, the fill character must
244 // too247 // be followed by an alignment specifier, unless it's '0' (zero)
245 const fill = comptime if (parser.peek(1)) |ch|248 // (in which case it's handled as part of the width specifier)
249 var fill: ?u21 = comptime if (parser.peek(1)) |ch|
246 switch (ch) {250 switch (ch) {
247 '<', '^', '>' => parser.char().?,251 '<', '^', '>' => parser.char(),
248 else => ' ',252 else => null,
249 }253 }
250 else254 else
251 ' ';255 null;
252256
253 // Parse the alignment parameter257 // 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: {
255 switch (ch) {259 switch (ch) {
256 '<', '^', '>' => _ = parser.char(),260 '<', '^', '>' => {
257 else => {},261 // consume the character
262 break :init switch (parser.char().?) {
263 '<' => .left,
264 '^' => .center,
265 else => .right,
266 };
267 },
268 else => break :init null,
258 }269 }
259 break :init switch (ch) {270 } else null;
260 '<' => .left,271
261 '^' => .center,272 // When none of the fill character and the alignment specifier have
262 else => .right,273 // been provided, check whether the width starts with a zero.
263 };274 if (fill == null and alignment == null) {
264 } else .right;275 fill = comptime if (parser.peek(0) == '0') '0' else null;
276 }
265277
266 // Parse the width parameter278 // Parse the width parameter
267 const width = comptime parser.specifier() catch |err|279 const width = comptime parser.specifier() catch |err|
...@@ -284,8 +296,8 @@ pub const Placeholder = struct {...@@ -284,8 +296,8 @@ pub const Placeholder = struct {
284296
285 return Placeholder{297 return Placeholder{
286 .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*),298 .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*),
287 .fill = fill,299 .fill = fill orelse default_fill_char,
288 .alignment = alignment,300 .alignment = alignment orelse default_alignment,
289 .arg = arg,301 .arg = arg,
290 .width = width,302 .width = width,
291 .precision = precision,303 .precision = precision,
...@@ -2648,6 +2660,15 @@ test "sci float padding" {...@@ -2648,6 +2660,15 @@ test "sci float padding" {
2648 try expectFmt("right-pad: 3.142e0****\n", "right-pad: {e:*<11.3}\n", .{number});2660 try expectFmt("right-pad: 3.142e0****\n", "right-pad: {e:*<11.3}\n", .{number});
2649}2661}
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
2651test "null" {2672test "null" {
2652 const inst = null;2673 const inst = null;
2653 try expectFmt("null", "{}", .{inst});2674 try expectFmt("null", "{}", .{inst});