authorgravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2019-06-20 20:07:43+12:00
committergravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2019-06-21 20:11:15+12:00
log11526b6e9da75ac682e59fbc2a37a738b8a23d6f
tree569edb8e4fc752b6486d049d36a6062854813343
parent381c6a38b145665a22440f7aa816f0ddd9b70ee5

breaking: Add positional, precision and width support to std.fmt

This removes the odd width and precision specifiers found and replacing them with the more consistent api described in #1358. Take the following example: {1:5.9} This refers to the first argument (0-indexed) in the argument list. It will be printed with a minimum width of 5 and will have a precision of 9 (if applicable). Not all types correctly use these parameters just yet. There are still some missing gaps to fill in. Fill characters and alignment have yet to be implemented.

5 files changed, 354 insertions(+), 367 deletions(-)

src-self-hosted/dep_tokenizer.zig+1-1
...@@ -999,7 +999,7 @@ fn printCharValues(out: var, bytes: []const u8) !void {...@@ -999,7 +999,7 @@ fn printCharValues(out: var, bytes: []const u8) !void {
999999
1000fn printUnderstandableChar(out: var, char: u8) !void {1000fn printUnderstandableChar(out: var, char: u8) !void {
1001 if (!std.ascii.isPrint(char) or char == ' ') {1001 if (!std.ascii.isPrint(char) or char == ' ') {
1002 std.fmt.format(out.context, anyerror, out.output, "\\x{X2}", char) catch {};1002 std.fmt.format(out.context, anyerror, out.output, "\\x{X:2}", char) catch {};
1003 } else {1003 } else {
1004 try out.write("'");1004 try out.write("'");
1005 try out.write([_]u8{printable_char_tab[char]});1005 try out.write([_]u8{printable_char_tab[char]});
std/fmt.zig+349-363
...@@ -10,6 +10,22 @@ const lossyCast = std.math.lossyCast;...@@ -10,6 +10,22 @@ const lossyCast = std.math.lossyCast;
1010
11pub const default_max_depth = 3;11pub const default_max_depth = 3;
1212
13pub const FormatOptions = struct {
14 precision: ?usize = null,
15 width: ?usize = null,
16};
17
18fn nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, comptime next_arg: *comptime_int) comptime_int {
19 if (maybe_pos_arg) |pos_arg| {
20 used_pos_args.* |= 1 << pos_arg;
21 return pos_arg;
22 } else {
23 const arg = next_arg.*;
24 next_arg.* += 1;
25 return arg;
26 }
27}
28
13/// Renders fmt string with args, calling output with slices of bytes.29/// Renders fmt string with args, calling output with slices of bytes.
14/// If `output` returns an error, the error is returned from `format` and30/// If `output` returns an error, the error is returned from `format` and
15/// `output` is not called again.31/// `output` is not called again.
...@@ -20,17 +36,29 @@ pub fn format(...@@ -20,17 +36,29 @@ pub fn format(
20 comptime fmt: []const u8,36 comptime fmt: []const u8,
21 args: ...,37 args: ...,
22) Errors!void {38) Errors!void {
39 const ArgSetType = @IntType(false, 32);
40 if (args.len > ArgSetType.bit_count) {
41 @compileError("32 arguments max are supported per format call");
42 }
43
23 const State = enum {44 const State = enum {
24 Start,45 Start,
25 OpenBrace,46 Positional,
26 CloseBrace,47 CloseBrace,
27 FormatString,48 Specifier,
49 FormatWidth,
50 FormatPrecision,
28 Pointer,51 Pointer,
29 };52 };
3053
31 comptime var start_index = 0;54 comptime var start_index = 0;
32 comptime var state = State.Start;55 comptime var state = State.Start;
33 comptime var next_arg = 0;56 comptime var next_arg = 0;
57 comptime var maybe_pos_arg: ?comptime_int = null;
58 comptime var used_pos_args: ArgSetType = 0;
59 comptime var specifier_start = 0;
60 comptime var specifier_end = 0;
61 comptime var options = FormatOptions{};
3462
35 inline for (fmt) |c, i| {63 inline for (fmt) |c, i| {
36 switch (state) {64 switch (state) {
...@@ -39,58 +67,165 @@ pub fn format(...@@ -39,58 +67,165 @@ pub fn format(
39 if (start_index < i) {67 if (start_index < i) {
40 try output(context, fmt[start_index..i]);68 try output(context, fmt[start_index..i]);
41 }69 }
70
42 start_index = i;71 start_index = i;
43 state = State.OpenBrace;72 specifier_start = i + 1;
73 specifier_end = i + 1;
74 maybe_pos_arg = null;
75 state = .Positional;
76 options = FormatOptions{};
44 },77 },
45
46 '}' => {78 '}' => {
47 if (start_index < i) {79 if (start_index < i) {
48 try output(context, fmt[start_index..i]);80 try output(context, fmt[start_index..i]);
49 }81 }
50 state = State.CloseBrace;82 state = .CloseBrace;
51 },83 },
52 else => {},84 else => {},
53 },85 },
54 .OpenBrace => switch (c) {86 .Positional => switch (c) {
55 '{' => {87 '{' => {
56 state = State.Start;88 state = .Start;
57 start_index = i;89 start_index = i;
58 },90 },
91 '*' => {
92 state = .Pointer;
93 },
94 ':' => {
95 state = .FormatWidth;
96 specifier_end = i;
97 },
98 '0'...'9' => {
99 if (maybe_pos_arg == null) {
100 maybe_pos_arg = 0;
101 }
102
103 maybe_pos_arg.? *= 10;
104 maybe_pos_arg.? += c - '0';
105 specifier_start = i + 1;
106
107 if (maybe_pos_arg.? >= args.len) {
108 @compileError("Positional value refers to non-existent argument");
109 }
110 },
59 '}' => {111 '}' => {
60 try formatType(args[next_arg], fmt[0..0], context, Errors, output, default_max_depth);112 const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg);
61 next_arg += 1;113
62 state = State.Start;114 try formatType(
115 args[arg_to_print],
116 fmt[0..0],
117 options,
118 context,
119 Errors,
120 output,
121 default_max_depth,
122 );
123
124 state = .Start;
63 start_index = i + 1;125 start_index = i + 1;
64 },126 },
65 '*' => state = State.Pointer,
66 else => {127 else => {
67 state = State.FormatString;128 state = .Specifier;
129 specifier_start = i;
68 },130 },
69 },131 },
70 .CloseBrace => switch (c) {132 .CloseBrace => switch (c) {
71 '}' => {133 '}' => {
72 state = State.Start;134 state = .Start;
73 start_index = i;135 start_index = i;
74 },136 },
75 else => @compileError("Single '}' encountered in format string"),137 else => @compileError("Single '}' encountered in format string"),
76 },138 },
77 .FormatString => switch (c) {139 .Specifier => switch (c) {
140 ':' => {
141 specifier_end = i;
142 state = .FormatWidth;
143 },
78 '}' => {144 '}' => {
79 const s = start_index + 1;145 const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg);
80 try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth);146
81 next_arg += 1;147 try formatType(
82 state = State.Start;148 args[arg_to_print],
149 fmt[specifier_start..i],
150 options,
151 context,
152 Errors,
153 output,
154 default_max_depth,
155 );
156 state = .Start;
83 start_index = i + 1;157 start_index = i + 1;
84 },158 },
85 else => {},159 else => {},
86 },160 },
161 .FormatWidth => switch (c) {
162 '0'...'9' => {
163 if (options.width == null) {
164 options.width = 0;
165 }
166
167 options.width.? *= 10;
168 options.width.? += c - '0';
169 },
170 '.' => {
171 state = .FormatPrecision;
172 },
173 '}' => {
174 const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg);
175
176 try formatType(
177 args[arg_to_print],
178 fmt[specifier_start..specifier_end],
179 options,
180 context,
181 Errors,
182 output,
183 default_max_depth,
184 );
185 state = .Start;
186 start_index = i + 1;
187 },
188 else => {
189 @compileError("Unexpected character in width value: " ++ [_]u8{c});
190 },
191 },
192 .FormatPrecision => switch (c) {
193 '0'...'9' => {
194 if (options.precision == null) {
195 options.precision = 0;
196 }
197
198 options.precision.? *= 10;
199 options.precision.? += c - '0';
200 },
201 '}' => {
202 const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg);
203
204 try formatType(
205 args[arg_to_print],
206 fmt[specifier_start..specifier_end],
207 options,
208 context,
209 Errors,
210 output,
211 default_max_depth,
212 );
213 state = .Start;
214 start_index = i + 1;
215 },
216 else => {
217 @compileError("Unexpected character in precision value: " ++ [_]u8{c});
218 },
219 },
87 .Pointer => switch (c) {220 .Pointer => switch (c) {
88 '}' => {221 '}' => {
89 try output(context, @typeName(@typeOf(args[next_arg]).Child));222 const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg);
223
224 try output(context, @typeName(@typeOf(args[arg_to_print]).Child));
90 try output(context, "@");225 try output(context, "@");
91 try formatInt(@ptrToInt(args[next_arg]), 16, false, 0, context, Errors, output);226 try formatInt(@ptrToInt(args[arg_to_print]), 16, false, 0, context, Errors, output);
92 next_arg += 1;227
93 state = State.Start;228 state = .Start;
94 start_index = i + 1;229 start_index = i + 1;
95 },230 },
96 else => @compileError("Unexpected format character after '*'"),231 else => @compileError("Unexpected format character after '*'"),
...@@ -98,7 +233,13 @@ pub fn format(...@@ -98,7 +233,13 @@ pub fn format(
98 }233 }
99 }234 }
100 comptime {235 comptime {
101 if (args.len != next_arg) {236 // All arguments must have been printed but we allow mixing positional and fixed to achieve this.
237 var i: usize = 0;
238 inline while (i < next_arg) : (i += 1) {
239 used_pos_args |= 1 << i;
240 }
241
242 if (@popCount(ArgSetType, used_pos_args) != args.len) {
102 @compileError("Unused arguments");243 @compileError("Unused arguments");
103 }244 }
104 if (state != State.Start) {245 if (state != State.Start) {
...@@ -113,6 +254,7 @@ pub fn format(...@@ -113,6 +254,7 @@ pub fn format(
113pub fn formatType(254pub fn formatType(
114 value: var,255 value: var,
115 comptime fmt: []const u8,256 comptime fmt: []const u8,
257 comptime options: FormatOptions,
116 context: var,258 context: var,
117 comptime Errors: type,259 comptime Errors: type,
118 output: fn (@typeOf(context), []const u8) Errors!void,260 output: fn (@typeOf(context), []const u8) Errors!void,
...@@ -121,7 +263,7 @@ pub fn formatType(...@@ -121,7 +263,7 @@ pub fn formatType(
121 const T = @typeOf(value);263 const T = @typeOf(value);
122 switch (@typeInfo(T)) {264 switch (@typeInfo(T)) {
123 .ComptimeInt, .Int, .Float => {265 .ComptimeInt, .Int, .Float => {
124 return formatValue(value, fmt, context, Errors, output);266 return formatValue(value, fmt, options, context, Errors, output);
125 },267 },
126 .Void => {268 .Void => {
127 return output(context, "void");269 return output(context, "void");
...@@ -131,16 +273,16 @@ pub fn formatType(...@@ -131,16 +273,16 @@ pub fn formatType(
131 },273 },
132 .Optional => {274 .Optional => {
133 if (value) |payload| {275 if (value) |payload| {
134 return formatType(payload, fmt, context, Errors, output, max_depth);276 return formatType(payload, fmt, options, context, Errors, output, max_depth);
135 } else {277 } else {
136 return output(context, "null");278 return output(context, "null");
137 }279 }
138 },280 },
139 .ErrorUnion => {281 .ErrorUnion => {
140 if (value) |payload| {282 if (value) |payload| {
141 return formatType(payload, fmt, context, Errors, output, max_depth);283 return formatType(payload, fmt, options, context, Errors, output, max_depth);
142 } else |err| {284 } else |err| {
143 return formatType(err, fmt, context, Errors, output, max_depth);285 return formatType(err, fmt, options, context, Errors, output, max_depth);
144 }286 }
145 },287 },
146 .ErrorSet => {288 .ErrorSet => {
...@@ -152,16 +294,16 @@ pub fn formatType(...@@ -152,16 +294,16 @@ pub fn formatType(
152 },294 },
153 .Enum => {295 .Enum => {
154 if (comptime std.meta.trait.hasFn("format")(T)) {296 if (comptime std.meta.trait.hasFn("format")(T)) {
155 return value.format(fmt, context, Errors, output);297 return value.format(fmt, options, context, Errors, output);
156 }298 }
157299
158 try output(context, @typeName(T));300 try output(context, @typeName(T));
159 try output(context, ".");301 try output(context, ".");
160 return formatType(@tagName(value), "", context, Errors, output, max_depth);302 return formatType(@tagName(value), "", options, context, Errors, output, max_depth);
161 },303 },
162 .Union => {304 .Union => {
163 if (comptime std.meta.trait.hasFn("format")(T)) {305 if (comptime std.meta.trait.hasFn("format")(T)) {
164 return value.format(fmt, context, Errors, output);306 return value.format(fmt, options, context, Errors, output);
165 }307 }
166308
167 try output(context, @typeName(T));309 try output(context, @typeName(T));
...@@ -175,7 +317,7 @@ pub fn formatType(...@@ -175,7 +317,7 @@ pub fn formatType(
175 try output(context, " = ");317 try output(context, " = ");
176 inline for (info.fields) |u_field| {318 inline for (info.fields) |u_field| {
177 if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) {319 if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) {
178 try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth - 1);320 try formatType(@field(value, u_field.name), "", options, context, Errors, output, max_depth - 1);
179 }321 }
180 }322 }
181 try output(context, " }");323 try output(context, " }");
...@@ -185,7 +327,7 @@ pub fn formatType(...@@ -185,7 +327,7 @@ pub fn formatType(
185 },327 },
186 .Struct => {328 .Struct => {
187 if (comptime std.meta.trait.hasFn("format")(T)) {329 if (comptime std.meta.trait.hasFn("format")(T)) {
188 return value.format(fmt, context, Errors, output);330 return value.format(fmt, options, context, Errors, output);
189 }331 }
190332
191 try output(context, @typeName(T));333 try output(context, @typeName(T));
...@@ -201,7 +343,7 @@ pub fn formatType(...@@ -201,7 +343,7 @@ pub fn formatType(
201 }343 }
202 try output(context, @memberName(T, field_i));344 try output(context, @memberName(T, field_i));
203 try output(context, " = ");345 try output(context, " = ");
204 try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth - 1);346 try formatType(@field(value, @memberName(T, field_i)), "", options, context, Errors, output, max_depth - 1);
205 }347 }
206 try output(context, " }");348 try output(context, " }");
207 },349 },
...@@ -209,12 +351,12 @@ pub fn formatType(...@@ -209,12 +351,12 @@ pub fn formatType(
209 .One => switch (@typeInfo(ptr_info.child)) {351 .One => switch (@typeInfo(ptr_info.child)) {
210 builtin.TypeId.Array => |info| {352 builtin.TypeId.Array => |info| {
211 if (info.child == u8) {353 if (info.child == u8) {
212 return formatText(value, fmt, context, Errors, output);354 return formatText(value, fmt, options, context, Errors, output);
213 }355 }
214 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));356 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));
215 },357 },
216 builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => {358 builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => {
217 return formatType(value.*, fmt, context, Errors, output, max_depth);359 return formatType(value.*, fmt, options, context, Errors, output, max_depth);
218 },360 },
219 else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)),361 else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)),
220 },362 },
...@@ -222,17 +364,17 @@ pub fn formatType(...@@ -222,17 +364,17 @@ pub fn formatType(
222 if (ptr_info.child == u8) {364 if (ptr_info.child == u8) {
223 if (fmt.len > 0 and fmt[0] == 's') {365 if (fmt.len > 0 and fmt[0] == 's') {
224 const len = mem.len(u8, value);366 const len = mem.len(u8, value);
225 return formatText(value[0..len], fmt, context, Errors, output);367 return formatText(value[0..len], fmt, options, context, Errors, output);
226 }368 }
227 }369 }
228 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));370 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));
229 },371 },
230 .Slice => {372 .Slice => {
231 if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) {373 if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) {
232 return formatText(value, fmt, context, Errors, output);374 return formatText(value, fmt, options, context, Errors, output);
233 }375 }
234 if (ptr_info.child == u8) {376 if (ptr_info.child == u8) {
235 return formatText(value, fmt, context, Errors, output);377 return formatText(value, fmt, options, context, Errors, output);
236 }378 }
237 return format(context, Errors, output, "{}@{x}", @typeName(ptr_info.child), @ptrToInt(value.ptr));379 return format(context, Errors, output, "{}@{x}", @typeName(ptr_info.child), @ptrToInt(value.ptr));
238 },380 },
...@@ -242,7 +384,7 @@ pub fn formatType(...@@ -242,7 +384,7 @@ pub fn formatType(
242 },384 },
243 .Array => |info| {385 .Array => |info| {
244 if (info.child == u8) {386 if (info.child == u8) {
245 return formatText(value, fmt, context, Errors, output);387 return formatText(value, fmt, options, context, Errors, output);
246 }388 }
247 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(&value));389 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(&value));
248 },390 },
...@@ -256,28 +398,23 @@ pub fn formatType(...@@ -256,28 +398,23 @@ pub fn formatType(
256fn formatValue(398fn formatValue(
257 value: var,399 value: var,
258 comptime fmt: []const u8,400 comptime fmt: []const u8,
401 comptime options: FormatOptions,
259 context: var,402 context: var,
260 comptime Errors: type,403 comptime Errors: type,
261 output: fn (@typeOf(context), []const u8) Errors!void,404 output: fn (@typeOf(context), []const u8) Errors!void,
262) Errors!void {405) Errors!void {
263 if (fmt.len > 0 and fmt[0] == 'B') {406 if (comptime std.mem.eql(u8, fmt, "B")) {
264 comptime var width: ?usize = null;407 if (options.width) |w| return formatBytes(value, w, 1000, context, Errors, output);
265 if (fmt.len > 1) {408 return formatBytes(value, null, 1000, context, Errors, output);
266 if (fmt[1] == 'i') {409 } else if (comptime std.mem.eql(u8, fmt, "Bi")) {
267 if (fmt.len > 2) {410 if (options.width) |w| return formatBytes(value, w, 1024, context, Errors, output);
268 width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable);411 return formatBytes(value, null, 1024, context, Errors, output);
269 }
270 return formatBytes(value, width, 1024, context, Errors, output);
271 }
272 width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);
273 }
274 return formatBytes(value, width, 1000, context, Errors, output);
275 }412 }
276413
277 const T = @typeOf(value);414 const T = @typeOf(value);
278 switch (@typeId(T)) {415 switch (@typeId(T)) {
279 .Float => return formatFloatValue(value, fmt, context, Errors, output),416 .Float => return formatFloatValue(value, fmt, options, context, Errors, output),
280 .Int, .ComptimeInt => return formatIntValue(value, fmt, context, Errors, output),417 .Int, .ComptimeInt => return formatIntValue(value, fmt, options, context, Errors, output),
281 else => comptime unreachable,418 else => comptime unreachable,
282 }419 }
283}420}
...@@ -285,13 +422,13 @@ fn formatValue(...@@ -285,13 +422,13 @@ fn formatValue(
285pub fn formatIntValue(422pub fn formatIntValue(
286 value: var,423 value: var,
287 comptime fmt: []const u8,424 comptime fmt: []const u8,
425 comptime options: FormatOptions,
288 context: var,426 context: var,
289 comptime Errors: type,427 comptime Errors: type,
290 output: fn (@typeOf(context), []const u8) Errors!void,428 output: fn (@typeOf(context), []const u8) Errors!void,
291) Errors!void {429) Errors!void {
292 comptime var radix = 10;430 comptime var radix = 10;
293 comptime var uppercase = false;431 comptime var uppercase = false;
294 comptime var width = 0;
295432
296 const int_value = if (@typeOf(value) == comptime_int) blk: {433 const int_value = if (@typeOf(value) == comptime_int) blk: {
297 const Int = math.IntFittingRange(value, value);434 const Int = math.IntFittingRange(value, value);
...@@ -299,83 +436,72 @@ pub fn formatIntValue(...@@ -299,83 +436,72 @@ pub fn formatIntValue(
299 } else436 } else
300 value;437 value;
301438
302 if (fmt.len > 0) {439 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) {
303 switch (fmt[0]) {440 radix = 10;
304 'c' => {441 uppercase = false;
305 if (@typeOf(int_value).bit_count <= 8) {442 } else if (comptime std.mem.eql(u8, fmt, "c")) {
306 if (fmt.len > 1)443 if (@typeOf(int_value).bit_count <= 8) {
307 @compileError("Unknown format character: " ++ [_]u8{fmt[1]});444 return formatAsciiChar(u8(int_value), context, Errors, output);
308 return formatAsciiChar(u8(int_value), context, Errors, output);445 } else {
309 }446 @compileError("Cannot print integer that is larger than 8 bits as a ascii");
310 },
311 'b' => {
312 radix = 2;
313 uppercase = false;
314 width = 0;
315 },
316 'd' => {
317 radix = 10;
318 uppercase = false;
319 width = 0;
320 },
321 'x' => {
322 radix = 16;
323 uppercase = false;
324 width = 0;
325 },
326 'X' => {
327 radix = 16;
328 uppercase = true;
329 width = 0;
330 },
331 else => @compileError("Unknown format character: " ++ [_]u8{fmt[0]}),
332 }447 }
333 if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);448 } else if (comptime std.mem.eql(u8, fmt, "b")) {
449 radix = 2;
450 uppercase = false;
451 } else if (comptime std.mem.eql(u8, fmt, "x")) {
452 radix = 16;
453 uppercase = false;
454 } else if (comptime std.mem.eql(u8, fmt, "X")) {
455 radix = 16;
456 uppercase = true;
457 } else {
458 @compileError("Unknown format string: '" ++ fmt ++ "'");
334 }459 }
335 return formatInt(int_value, radix, uppercase, width, context, Errors, output);460
461 if (options.width) |w| return formatInt(int_value, radix, uppercase, w, context, Errors, output);
462 return formatInt(int_value, radix, uppercase, 0, context, Errors, output);
336}463}
337464
338fn formatFloatValue(465fn formatFloatValue(
339 value: var,466 value: var,
340 comptime fmt: []const u8,467 comptime fmt: []const u8,
468 comptime options: FormatOptions,
341 context: var,469 context: var,
342 comptime Errors: type,470 comptime Errors: type,
343 output: fn (@typeOf(context), []const u8) Errors!void,471 output: fn (@typeOf(context), []const u8) Errors!void,
344) Errors!void {472) Errors!void {
345 comptime var width: ?usize = null;473 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {
346 comptime var float_fmt = 'e';474 if (options.precision) |p| return formatFloatScientific(value, p, context, Errors, output);
347 if (fmt.len > 0) {475 return formatFloatScientific(value, null, context, Errors, output);
348 float_fmt = fmt[0];476 } else if (comptime std.mem.eql(u8, fmt, "d")) {
349 if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);477 if (options.precision) |p| return formatFloatDecimal(value, p, context, Errors, output);
350 }478 return formatFloatDecimal(value, options.precision, context, Errors, output);
351479 } else {
352 switch (float_fmt) {480 @compileError("Unknown format string: '" ++ fmt ++ "'");
353 'e' => try formatFloatScientific(value, width, context, Errors, output),
354 '.' => try formatFloatDecimal(value, width, context, Errors, output),
355 else => @compileError("Unknown format character: " ++ [_]u8{float_fmt}),
356 }481 }
357}482}
358483
359pub fn formatText(484pub fn formatText(
360 bytes: []const u8,485 bytes: []const u8,
361 comptime fmt: []const u8,486 comptime fmt: []const u8,
487 comptime options: FormatOptions,
362 context: var,488 context: var,
363 comptime Errors: type,489 comptime Errors: type,
364 output: fn (@typeOf(context), []const u8) Errors!void,490 output: fn (@typeOf(context), []const u8) Errors!void,
365) Errors!void {491) Errors!void {
366 if (fmt.len > 0) {492 if (fmt.len == 0) {
367 if (fmt[0] == 's') {493 return output(context, bytes);
368 comptime var width = 0;494 } else if (comptime std.mem.eql(u8, fmt, "s")) {
369 if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);495 if (options.width) |w| return formatBuf(bytes, w, context, Errors, output);
370 return formatBuf(bytes, width, context, Errors, output);496 return formatBuf(bytes, 0, context, Errors, output);
371 } else if ((fmt[0] == 'x') or (fmt[0] == 'X')) {497 } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) {
372 for (bytes) |c| {498 for (bytes) |c| {
373 try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output);499 try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output);
374 }500 }
375 return;501 return;
376 } else @compileError("Unknown format character: " ++ [_]u8{fmt[0]});502 } else {
503 @compileError("Unknown format string: '" ++ fmt ++ "'");
377 }504 }
378 return output(context, bytes);
379}505}
380506
381pub fn formatAsciiChar(507pub fn formatAsciiChar(
...@@ -868,7 +994,7 @@ test "parseUnsigned" {...@@ -868,7 +994,7 @@ test "parseUnsigned" {
868994
869pub const parseFloat = @import("fmt/parse_float.zig").parseFloat;995pub const parseFloat = @import("fmt/parse_float.zig").parseFloat;
870996
871test "fmt.parseFloat" {997test "parseFloat" {
872 _ = @import("fmt/parse_float.zig");998 _ = @import("fmt/parse_float.zig");
873}999}
8741000
...@@ -960,7 +1086,7 @@ test "parse unsigned comptime" {...@@ -960,7 +1086,7 @@ test "parse unsigned comptime" {
960 }1086 }
961}1087}
9621088
963test "fmt.optional" {1089test "optional" {
964 {1090 {
965 const value: ?i32 = 1234;1091 const value: ?i32 = 1234;
966 try testFmt("optional: 1234\n", "optional: {}\n", value);1092 try testFmt("optional: 1234\n", "optional: {}\n", value);
...@@ -971,7 +1097,7 @@ test "fmt.optional" {...@@ -971,7 +1097,7 @@ test "fmt.optional" {
971 }1097 }
972}1098}
9731099
974test "fmt.error" {1100test "error" {
975 {1101 {
976 const value: anyerror!i32 = 1234;1102 const value: anyerror!i32 = 1234;
977 try testFmt("error union: 1234\n", "error union: {}\n", value);1103 try testFmt("error union: 1234\n", "error union: {}\n", value);
...@@ -982,14 +1108,14 @@ test "fmt.error" {...@@ -982,14 +1108,14 @@ test "fmt.error" {
982 }1108 }
983}1109}
9841110
985test "fmt.int.small" {1111test "int.small" {
986 {1112 {
987 const value: u3 = 0b101;1113 const value: u3 = 0b101;
988 try testFmt("u3: 5\n", "u3: {}\n", value);1114 try testFmt("u3: 5\n", "u3: {}\n", value);
989 }1115 }
990}1116}
9911117
992test "fmt.int.specifier" {1118test "int.specifier" {
993 {1119 {
994 const value: u8 = 'a';1120 const value: u8 = 'a';
995 try testFmt("u8: a\n", "u8: {c}\n", value);1121 try testFmt("u8: a\n", "u8: {c}\n", value);
...@@ -1000,27 +1126,31 @@ test "fmt.int.specifier" {...@@ -1000,27 +1126,31 @@ test "fmt.int.specifier" {
1000 }1126 }
1001}1127}
10021128
1003test "fmt.buffer" {1129test "int.padded" {
1130 try testFmt("u8: '0001'", "u8: '{:4}'", u8(1));
1131}
1132
1133test "buffer" {
1004 {1134 {
1005 var buf1: [32]u8 = undefined;1135 var buf1: [32]u8 = undefined;
1006 var context = BufPrintContext{ .remaining = buf1[0..] };1136 var context = BufPrintContext{ .remaining = buf1[0..] };
1007 try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);1137 try formatType(1234, "", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
1008 var res = buf1[0 .. buf1.len - context.remaining.len];1138 var res = buf1[0 .. buf1.len - context.remaining.len];
1009 testing.expect(mem.eql(u8, res, "1234"));1139 testing.expect(mem.eql(u8, res, "1234"));
10101140
1011 context = BufPrintContext{ .remaining = buf1[0..] };1141 context = BufPrintContext{ .remaining = buf1[0..] };
1012 try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);1142 try formatType('a', "c", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
1013 res = buf1[0 .. buf1.len - context.remaining.len];1143 res = buf1[0 .. buf1.len - context.remaining.len];
1014 testing.expect(mem.eql(u8, res, "a"));1144 testing.expect(mem.eql(u8, res, "a"));
10151145
1016 context = BufPrintContext{ .remaining = buf1[0..] };1146 context = BufPrintContext{ .remaining = buf1[0..] };
1017 try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);1147 try formatType(0b1100, "b", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
1018 res = buf1[0 .. buf1.len - context.remaining.len];1148 res = buf1[0 .. buf1.len - context.remaining.len];
1019 testing.expect(mem.eql(u8, res, "1100"));1149 testing.expect(mem.eql(u8, res, "1100"));
1020 }1150 }
1021}1151}
10221152
1023test "fmt.array" {1153test "array" {
1024 {1154 {
1025 const value: [3]u8 = "abc";1155 const value: [3]u8 = "abc";
1026 try testFmt("array: abc\n", "array: {}\n", value);1156 try testFmt("array: abc\n", "array: {}\n", value);
...@@ -1035,7 +1165,7 @@ test "fmt.array" {...@@ -1035,7 +1165,7 @@ test "fmt.array" {
1035 }1165 }
1036}1166}
10371167
1038test "fmt.slice" {1168test "slice" {
1039 {1169 {
1040 const value: []const u8 = "abc";1170 const value: []const u8 = "abc";
1041 try testFmt("slice: abc\n", "slice: {}\n", value);1171 try testFmt("slice: abc\n", "slice: {}\n", value);
...@@ -1045,11 +1175,11 @@ test "fmt.slice" {...@@ -1045,11 +1175,11 @@ test "fmt.slice" {
1045 try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", value);1175 try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", value);
1046 }1176 }
10471177
1048 try testFmt("buf: Test \n", "buf: {s5}\n", "Test");1178 try testFmt("buf: Test \n", "buf: {s:5}\n", "Test");
1049 try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test");1179 try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test");
1050}1180}
10511181
1052test "fmt.pointer" {1182test "pointer" {
1053 {1183 {
1054 const value = @intToPtr(*i32, 0xdeadbeef);1184 const value = @intToPtr(*i32, 0xdeadbeef);
1055 try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value);1185 try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value);
...@@ -1065,17 +1195,17 @@ test "fmt.pointer" {...@@ -1065,17 +1195,17 @@ test "fmt.pointer" {
1065 }1195 }
1066}1196}
10671197
1068test "fmt.cstr" {1198test "cstr" {
1069 try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C");1199 try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C");
1070 try testFmt("cstr: Test C \n", "cstr: {s10}\n", c"Test C");1200 try testFmt("cstr: Test C \n", "cstr: {s:10}\n", c"Test C");
1071}1201}
10721202
1073test "fmt.filesize" {1203test "filesize" {
1074 try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024));1204 try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024));
1075 try testFmt("file size: 66.06MB\n", "file size: {B2}\n", usize(63 * 1024 * 1024));1205 try testFmt("file size: 66.06MB\n", "file size: {B:2}\n", usize(63 * 1024 * 1024));
1076}1206}
10771207
1078test "fmt.struct" {1208test "struct" {
1079 {1209 {
1080 const Struct = struct {1210 const Struct = struct {
1081 field: u8,1211 field: u8,
...@@ -1094,7 +1224,7 @@ test "fmt.struct" {...@@ -1094,7 +1224,7 @@ test "fmt.struct" {
1094 }1224 }
1095}1225}
10961226
1097test "fmt.enum" {1227test "enum" {
1098 const Enum = enum {1228 const Enum = enum {
1099 One,1229 One,
1100 Two,1230 Two,
...@@ -1104,229 +1234,71 @@ test "fmt.enum" {...@@ -1104,229 +1234,71 @@ test "fmt.enum" {
1104 try testFmt("enum: Enum.Two\n", "enum: {}\n", &value);1234 try testFmt("enum: Enum.Two\n", "enum: {}\n", &value);
1105}1235}
11061236
1107test "fmt.float.scientific" {1237test "float.scientific" {
1108 {1238 try testFmt("f32: 1.34000003e+00", "f32: {e}", f32(1.34));
1109 var buf1: [32]u8 = undefined;1239 try testFmt("f32: 1.23400001e+01", "f32: {e}", f32(12.34));
1110 const value: f32 = 1.34;1240 try testFmt("f64: -1.234e+11", "f64: {e}", f64(-12.34e10));
1111 const result = try bufPrint(buf1[0..], "f32: {e}\n", value);1241 try testFmt("f64: 9.99996e-40", "f64: {e}", f64(9.999960e-40));
1112 testing.expect(mem.eql(u8, result, "f32: 1.34000003e+00\n"));
1113 }
1114 {
1115 var buf1: [32]u8 = undefined;
1116 const value: f32 = 12.34;
1117 const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
1118 testing.expect(mem.eql(u8, result, "f32: 1.23400001e+01\n"));
1119 }
1120 {
1121 var buf1: [32]u8 = undefined;
1122 const value: f64 = -12.34e10;
1123 const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
1124 testing.expect(mem.eql(u8, result, "f64: -1.234e+11\n"));
1125 }
1126 {
1127 // This fails on release due to a minor rounding difference.
1128 // --release-fast outputs 9.999960000000001e-40 vs. the expected.
1129 // TODO fix this, it should be the same in Debug and ReleaseFast
1130 if (builtin.mode == builtin.Mode.Debug) {
1131 var buf1: [32]u8 = undefined;
1132 const value: f64 = 9.999960e-40;
1133 const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
1134 testing.expect(mem.eql(u8, result, "f64: 9.99996e-40\n"));
1135 }
1136 }
1137}1242}
11381243
1139test "fmt.float.scientific.precision" {1244test "float.scientific.precision" {
1140 {1245 try testFmt("f64: 1.40971e-42", "f64: {e:.5}", f64(1.409706e-42));
1141 var buf1: [32]u8 = undefined;1246 try testFmt("f64: 1.00000e-09", "f64: {e:.5}", f64(@bitCast(f32, u32(814313563))));
1142 const value: f64 = 1.409706e-42;1247 try testFmt("f64: 7.81250e-03", "f64: {e:.5}", f64(@bitCast(f32, u32(1006632960))));
1143 const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);1248 // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05.
1144 testing.expect(mem.eql(u8, result, "f64: 1.40971e-42\n"));1249 // In fact, libc doesn't round a lot of 5 cases up when one past the precision point.
1145 }1250 try testFmt("f64: 1.00001e+05", "f64: {e:.5}", f64(@bitCast(f32, u32(1203982400))));
1146 {
1147 var buf1: [32]u8 = undefined;
1148 const value: f64 = @bitCast(f32, u32(814313563));
1149 const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
1150 testing.expect(mem.eql(u8, result, "f64: 1.00000e-09\n"));
1151 }
1152 {
1153 var buf1: [32]u8 = undefined;
1154 const value: f64 = @bitCast(f32, u32(1006632960));
1155 const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
1156 testing.expect(mem.eql(u8, result, "f64: 7.81250e-03\n"));
1157 }
1158 {
1159 // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05.
1160 // In fact, libc doesn't round a lot of 5 cases up when one past the precision point.
1161 var buf1: [32]u8 = undefined;
1162 const value: f64 = @bitCast(f32, u32(1203982400));
1163 const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
1164 testing.expect(mem.eql(u8, result, "f64: 1.00001e+05\n"));
1165 }
1166}1251}
11671252
1168test "fmt.float.special" {1253test "float.special" {
1169 {1254 try testFmt("f64: nan", "f64: {}", math.nan_f64);
1170 var buf1: [32]u8 = undefined;1255 // negative nan is not defined by IEE 754,
1171 const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);1256 // and ARM thus normalizes it to positive nan
1172 testing.expect(mem.eql(u8, result, "f64: nan\n"));
1173 }
1174 if (builtin.arch != builtin.Arch.arm) {1257 if (builtin.arch != builtin.Arch.arm) {
1175 // negative nan is not defined by IEE 754,1258 try testFmt("f64: -nan", "f64: {}", -math.nan_f64);
1176 // and ARM thus normalizes it to positive nan
1177 var buf1: [32]u8 = undefined;
1178 const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64);
1179 testing.expect(mem.eql(u8, result, "f64: -nan\n"));
1180 }
1181 {
1182 var buf1: [32]u8 = undefined;
1183 const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64);
1184 testing.expect(mem.eql(u8, result, "f64: inf\n"));
1185 }
1186 {
1187 var buf1: [32]u8 = undefined;
1188 const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);
1189 testing.expect(mem.eql(u8, result, "f64: -inf\n"));
1190 }1259 }
1260 try testFmt("f64: inf", "f64: {}", math.inf_f64);
1261 try testFmt("f64: -inf", "f64: {}", -math.inf_f64);
1191}1262}
11921263
1193test "fmt.float.decimal" {1264test "float.decimal" {
1194 {1265 try testFmt("f64: 152314000000000000000000000000", "f64: {d}", f64(1.52314e+29));
1195 var buf1: [64]u8 = undefined;1266 try testFmt("f32: 1.1", "f32: {d:.1}", f32(1.1234));
1196 const value: f64 = 1.52314e+29;1267 try testFmt("f32: 1234.57", "f32: {d:.2}", f32(1234.567));
1197 const result = try bufPrint(buf1[0..], "f64: {.}\n", value);1268 // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
1198 testing.expect(mem.eql(u8, result, "f64: 152314000000000000000000000000\n"));1269 // -11.12339... is rounded back up to -11.1234
1199 }1270 try testFmt("f32: -11.1234", "f32: {d:.4}", f32(-11.1234));
1200 {1271 try testFmt("f32: 91.12345", "f32: {d:.5}", f32(91.12345));
1201 var buf1: [32]u8 = undefined;1272 try testFmt("f64: 91.1234567890", "f64: {d:.10}", f64(91.12345678901235));
1202 const value: f32 = 1.1234;1273 try testFmt("f64: 0.00000", "f64: {d:.5}", f64(0.0));
1203 const result = try bufPrint(buf1[0..], "f32: {.1}\n", value);1274 try testFmt("f64: 6", "f64: {d:.0}", f64(5.700));
1204 testing.expect(mem.eql(u8, result, "f32: 1.1\n"));1275 try testFmt("f64: 10.0", "f64: {d:.1}", f64(9.999));
1205 }1276 try testFmt("f64: 1.000", "f64: {d:.3}", f64(1.0));
1206 {1277 try testFmt("f64: 0.00030000", "f64: {d:.8}", f64(0.0003));
1207 var buf1: [32]u8 = undefined;1278 try testFmt("f64: 0.00000", "f64: {d:.5}", f64(1.40130e-45));
1208 const value: f32 = 1234.567;1279 try testFmt("f64: 0.00000", "f64: {d:.5}", f64(9.999960e-40));
1209 const result = try bufPrint(buf1[0..], "f32: {.2}\n", value);
1210 testing.expect(mem.eql(u8, result, "f32: 1234.57\n"));
1211 }
1212 {
1213 var buf1: [32]u8 = undefined;
1214 const value: f32 = -11.1234;
1215 const result = try bufPrint(buf1[0..], "f32: {.4}\n", value);
1216 // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
1217 // -11.12339... is rounded back up to -11.1234
1218 testing.expect(mem.eql(u8, result, "f32: -11.1234\n"));
1219 }
1220 {
1221 var buf1: [32]u8 = undefined;
1222 const value: f32 = 91.12345;
1223 const result = try bufPrint(buf1[0..], "f32: {.5}\n", value);
1224 testing.expect(mem.eql(u8, result, "f32: 91.12345\n"));
1225 }
1226 {
1227 var buf1: [32]u8 = undefined;
1228 const value: f64 = 91.12345678901235;
1229 const result = try bufPrint(buf1[0..], "f64: {.10}\n", value);
1230 testing.expect(mem.eql(u8, result, "f64: 91.1234567890\n"));
1231 }
1232 {
1233 var buf1: [32]u8 = undefined;
1234 const value: f64 = 0.0;
1235 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1236 testing.expect(mem.eql(u8, result, "f64: 0.00000\n"));
1237 }
1238 {
1239 var buf1: [32]u8 = undefined;
1240 const value: f64 = 5.700;
1241 const result = try bufPrint(buf1[0..], "f64: {.0}\n", value);
1242 testing.expect(mem.eql(u8, result, "f64: 6\n"));
1243 }
1244 {
1245 var buf1: [32]u8 = undefined;
1246 const value: f64 = 9.999;
1247 const result = try bufPrint(buf1[0..], "f64: {.1}\n", value);
1248 testing.expect(mem.eql(u8, result, "f64: 10.0\n"));
1249 }
1250 {
1251 var buf1: [32]u8 = undefined;
1252 const value: f64 = 1.0;
1253 const result = try bufPrint(buf1[0..], "f64: {.3}\n", value);
1254 testing.expect(mem.eql(u8, result, "f64: 1.000\n"));
1255 }
1256 {
1257 var buf1: [32]u8 = undefined;
1258 const value: f64 = 0.0003;
1259 const result = try bufPrint(buf1[0..], "f64: {.8}\n", value);
1260 testing.expect(mem.eql(u8, result, "f64: 0.00030000\n"));
1261 }
1262 {
1263 var buf1: [32]u8 = undefined;
1264 const value: f64 = 1.40130e-45;
1265 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1266 testing.expect(mem.eql(u8, result, "f64: 0.00000\n"));
1267 }
1268 {
1269 var buf1: [32]u8 = undefined;
1270 const value: f64 = 9.999960e-40;
1271 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1272 testing.expect(mem.eql(u8, result, "f64: 0.00000\n"));
1273 }
1274}1280}
12751281
1276test "fmt.float.libc.sanity" {1282test "float.libc.sanity" {
1277 {1283 try testFmt("f64: 0.00001", "f64: {d:.5}", f64(@bitCast(f32, u32(916964781))));
1278 var buf1: [32]u8 = undefined;1284 try testFmt("f64: 0.00001", "f64: {d:.5}", f64(@bitCast(f32, u32(925353389))));
1279 const value: f64 = f64(@bitCast(f32, u32(916964781)));1285 try testFmt("f64: 0.10000", "f64: {d:.5}", f64(@bitCast(f32, u32(1036831278))));
1280 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);1286 try testFmt("f64: 1.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1065353133))));
1281 testing.expect(mem.eql(u8, result, "f64: 0.00001\n"));1287 try testFmt("f64: 10.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1092616192))));
1282 }1288
1283 {
1284 var buf1: [32]u8 = undefined;
1285 const value: f64 = f64(@bitCast(f32, u32(925353389)));
1286 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1287 testing.expect(mem.eql(u8, result, "f64: 0.00001\n"));
1288 }
1289 {
1290 var buf1: [32]u8 = undefined;
1291 const value: f64 = f64(@bitCast(f32, u32(1036831278)));
1292 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1293 testing.expect(mem.eql(u8, result, "f64: 0.10000\n"));
1294 }
1295 {
1296 var buf1: [32]u8 = undefined;
1297 const value: f64 = f64(@bitCast(f32, u32(1065353133)));
1298 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1299 testing.expect(mem.eql(u8, result, "f64: 1.00000\n"));
1300 }
1301 {
1302 var buf1: [32]u8 = undefined;
1303 const value: f64 = f64(@bitCast(f32, u32(1092616192)));
1304 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1305 testing.expect(mem.eql(u8, result, "f64: 10.00000\n"));
1306 }
1307 // libc differences1289 // libc differences
1308 {1290 //
1309 var buf1: [32]u8 = undefined;1291 // This is 0.015625 exactly according to gdb. We thus round down,
1310 // This is 0.015625 exactly according to gdb. We thus round down,1292 // however glibc rounds up for some reason. This occurs for all
1311 // however glibc rounds up for some reason. This occurs for all1293 // floats of the form x.yyyy25 on a precision point.
1312 // floats of the form x.yyyy25 on a precision point.1294 try testFmt("f64: 0.01563", "f64: {d:.5}", f64(@bitCast(f32, u32(1015021568))));
1313 const value: f64 = f64(@bitCast(f32, u32(1015021568)));1295 // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3
1314 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);1296 // also rounds to 630 so I'm inclined to believe libc is not
1315 testing.expect(mem.eql(u8, result, "f64: 0.01563\n"));1297 // optimal here.
1316 }1298 try testFmt("f64: 18014400656965630.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1518338049))));
1317 // std-windows-x86_64-Debug-bare test case fails
1318 {
1319 // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3
1320 // also rounds to 630 so I'm inclined to believe libc is not
1321 // optimal here.
1322 var buf1: [32]u8 = undefined;
1323 const value: f64 = f64(@bitCast(f32, u32(1518338049)));
1324 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
1325 testing.expect(mem.eql(u8, result, "f64: 18014400656965630.00000\n"));
1326 }
1327}1299}
13281300
1329test "fmt.custom" {1301test "custom" {
1330 const Vec2 = struct {1302 const Vec2 = struct {
1331 const SelfType = @This();1303 const SelfType = @This();
1332 x: f32,1304 x: f32,
...@@ -1335,20 +1307,17 @@ test "fmt.custom" {...@@ -1335,20 +1307,17 @@ test "fmt.custom" {
1335 pub fn format(1307 pub fn format(
1336 self: SelfType,1308 self: SelfType,
1337 comptime fmt: []const u8,1309 comptime fmt: []const u8,
1310 comptime options: FormatOptions,
1338 context: var,1311 context: var,
1339 comptime Errors: type,1312 comptime Errors: type,
1340 output: fn (@typeOf(context), []const u8) Errors!void,1313 output: fn (@typeOf(context), []const u8) Errors!void,
1341 ) Errors!void {1314 ) Errors!void {
1342 switch (fmt.len) {1315 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) {
1343 0 => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y),1316 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", self.x, self.y);
1344 1 => switch (fmt[0]) {1317 } else if (comptime std.mem.eql(u8, fmt, "d")) {
1345 //point format1318 return std.fmt.format(context, Errors, output, "{d:.3}x{d:.3}", self.x, self.y);
1346 'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y),1319 } else {
1347 //dimension format1320 @compileError("Unknown format character: '" ++ fmt ++ "'");
1348 'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y),
1349 else => unreachable,
1350 },
1351 else => unreachable,
1352 }1321 }
1353 }1322 }
1354 };1323 };
...@@ -1366,7 +1335,7 @@ test "fmt.custom" {...@@ -1366,7 +1335,7 @@ test "fmt.custom" {
1366 try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value);1335 try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value);
1367}1336}
13681337
1369test "fmt.struct" {1338test "struct" {
1370 const S = struct {1339 const S = struct {
1371 a: u32,1340 a: u32,
1372 b: anyerror,1341 b: anyerror,
...@@ -1380,7 +1349,7 @@ test "fmt.struct" {...@@ -1380,7 +1349,7 @@ test "fmt.struct" {
1380 try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst);1349 try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst);
1381}1350}
13821351
1383test "fmt.union" {1352test "union" {
1384 const TU = union(enum) {1353 const TU = union(enum) {
1385 float: f32,1354 float: f32,
1386 int: u32,1355 int: u32,
...@@ -1410,7 +1379,7 @@ test "fmt.union" {...@@ -1410,7 +1379,7 @@ test "fmt.union" {
1410 testing.expect(mem.eql(u8, uu_result[0..3], "EU@"));1379 testing.expect(mem.eql(u8, uu_result[0..3], "EU@"));
1411}1380}
14121381
1413test "fmt.enum" {1382test "enum" {
1414 const E = enum {1383 const E = enum {
1415 One,1384 One,
1416 Two,1385 Two,
...@@ -1422,7 +1391,7 @@ test "fmt.enum" {...@@ -1422,7 +1391,7 @@ test "fmt.enum" {
1422 try testFmt("E.Two", "{}", inst);1391 try testFmt("E.Two", "{}", inst);
1423}1392}
14241393
1425test "fmt.struct.self-referential" {1394test "struct.self-referential" {
1426 const S = struct {1395 const S = struct {
1427 const SelfType = @This();1396 const SelfType = @This();
1428 a: ?*SelfType,1397 a: ?*SelfType,
...@@ -1436,7 +1405,7 @@ test "fmt.struct.self-referential" {...@@ -1436,7 +1405,7 @@ test "fmt.struct.self-referential" {
1436 try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst);1405 try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst);
1437}1406}
14381407
1439test "fmt.bytes.hex" {1408test "bytes.hex" {
1440 const some_bytes = "\xCA\xFE\xBA\xBE";1409 const some_bytes = "\xCA\xFE\xBA\xBE";
1441 try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes);1410 try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes);
1442 try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes);1411 try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes);
...@@ -1478,7 +1447,7 @@ pub fn trim(buf: []const u8) []const u8 {...@@ -1478,7 +1447,7 @@ pub fn trim(buf: []const u8) []const u8 {
1478 return buf[start..end];1447 return buf[start..end];
1479}1448}
14801449
1481test "fmt.trim" {1450test "trim" {
1482 testing.expect(mem.eql(u8, "abc", trim("\n abc \t")));1451 testing.expect(mem.eql(u8, "abc", trim("\n abc \t")));
1483 testing.expect(mem.eql(u8, "", trim(" ")));1452 testing.expect(mem.eql(u8, "", trim(" ")));
1484 testing.expect(mem.eql(u8, "", trim("")));1453 testing.expect(mem.eql(u8, "", trim("")));
...@@ -1505,22 +1474,22 @@ pub fn hexToBytes(out: []u8, input: []const u8) !void {...@@ -1505,22 +1474,22 @@ pub fn hexToBytes(out: []u8, input: []const u8) !void {
1505 }1474 }
1506}1475}
15071476
1508test "fmt.hexToBytes" {1477test "hexToBytes" {
1509 const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706";1478 const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706";
1510 var pb: [32]u8 = undefined;1479 var pb: [32]u8 = undefined;
1511 try hexToBytes(pb[0..], test_hex_str);1480 try hexToBytes(pb[0..], test_hex_str);
1512 try testFmt(test_hex_str, "{X}", pb);1481 try testFmt(test_hex_str, "{X}", pb);
1513}1482}
15141483
1515test "fmt.formatIntValue with comptime_int" {1484test "formatIntValue with comptime_int" {
1516 const value: comptime_int = 123456789123456789;1485 const value: comptime_int = 123456789123456789;
15171486
1518 var buf = try std.Buffer.init(std.debug.global_allocator, "");1487 var buf = try std.Buffer.init(std.debug.global_allocator, "");
1519 try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append);1488 try formatIntValue(value, "", FormatOptions{}, &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append);
1520 assert(mem.eql(u8, buf.toSlice(), "123456789123456789"));1489 assert(mem.eql(u8, buf.toSlice(), "123456789123456789"));
1521}1490}
15221491
1523test "fmt.formatType max_depth" {1492test "formatType max_depth" {
1524 const Vec2 = struct {1493 const Vec2 = struct {
1525 const SelfType = @This();1494 const SelfType = @This();
1526 x: f32,1495 x: f32,
...@@ -1529,11 +1498,16 @@ test "fmt.formatType max_depth" {...@@ -1529,11 +1498,16 @@ test "fmt.formatType max_depth" {
1529 pub fn format(1498 pub fn format(
1530 self: SelfType,1499 self: SelfType,
1531 comptime fmt: []const u8,1500 comptime fmt: []const u8,
1501 comptime options: FormatOptions,
1532 context: var,1502 context: var,
1533 comptime Errors: type,1503 comptime Errors: type,
1534 output: fn (@typeOf(context), []const u8) Errors!void,1504 output: fn (@typeOf(context), []const u8) Errors!void,
1535 ) Errors!void {1505 ) Errors!void {
1536 return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y);1506 if (fmt.len == 0) {
1507 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", self.x, self.y);
1508 } else {
1509 @compileError("Unknown format string: '" ++ fmt ++ "'");
1510 }
1537 }1511 }
1538 };1512 };
1539 const E = enum {1513 const E = enum {
...@@ -1565,18 +1539,30 @@ test "fmt.formatType max_depth" {...@@ -1565,18 +1539,30 @@ test "fmt.formatType max_depth" {
1565 inst.tu.ptr = &inst.tu;1539 inst.tu.ptr = &inst.tu;
15661540
1567 var buf0 = try std.Buffer.init(std.debug.global_allocator, "");1541 var buf0 = try std.Buffer.init(std.debug.global_allocator, "");
1568 try formatType(inst, "", &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0);1542 try formatType(inst, "", FormatOptions{}, &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0);
1569 assert(mem.eql(u8, buf0.toSlice(), "S{ ... }"));1543 assert(mem.eql(u8, buf0.toSlice(), "S{ ... }"));
15701544
1571 var buf1 = try std.Buffer.init(std.debug.global_allocator, "");1545 var buf1 = try std.Buffer.init(std.debug.global_allocator, "");
1572 try formatType(inst, "", &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1);1546 try formatType(inst, "", FormatOptions{}, &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1);
1573 assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }"));1547 assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }"));
15741548
1575 var buf2 = try std.Buffer.init(std.debug.global_allocator, "");1549 var buf2 = try std.Buffer.init(std.debug.global_allocator, "");
1576 try formatType(inst, "", &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2);1550 try formatType(inst, "", FormatOptions{}, &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2);
1577 assert(mem.eql(u8, buf2.toSlice(), "S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }"));1551 assert(mem.eql(u8, buf2.toSlice(), "S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }"));
15781552
1579 var buf3 = try std.Buffer.init(std.debug.global_allocator, "");1553 var buf3 = try std.Buffer.init(std.debug.global_allocator, "");
1580 try formatType(inst, "", &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3);1554 try formatType(inst, "", FormatOptions{}, &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3);
1581 assert(mem.eql(u8, buf3.toSlice(), "S{ .a = S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ .ptr = TU{ ... } } }, .e = E.Two, .vec = (10.200,2.220) }"));1555 assert(mem.eql(u8, buf3.toSlice(), "S{ .a = S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ .ptr = TU{ ... } } }, .e = E.Two, .vec = (10.200,2.220) }"));
1582}1556}
1557
1558test "positional" {
1559 try testFmt("2 1 0", "{2} {1} {0}", usize(0), usize(1), usize(2));
1560 try testFmt("2 1 0", "{2} {1} {}", usize(0), usize(1), usize(2));
1561 try testFmt("0 0", "{0} {0}", usize(0));
1562 try testFmt("0 1", "{} {1}", usize(0), usize(1));
1563 try testFmt("1 0 0 1", "{1} {} {0} {}", usize(0), usize(1));
1564}
1565
1566test "positional with specifier" {
1567 try testFmt("10.0", "{0d:.1}", f64(9.999));
1568}
std/math/big/int.zig+1
...@@ -519,6 +519,7 @@ pub const Int = struct {...@@ -519,6 +519,7 @@ pub const Int = struct {
519 pub fn format(519 pub fn format(
520 self: Int,520 self: Int,
521 comptime fmt: []const u8,521 comptime fmt: []const u8,
522 comptime options: std.fmt.FormatOptions,
522 context: var,523 context: var,
523 comptime FmtError: type,524 comptime FmtError: type,
524 output: fn (@typeOf(context), []const u8) FmtError!void,525 output: fn (@typeOf(context), []const u8) FmtError!void,
std/special/build_runner.zig+2-2
...@@ -170,7 +170,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void {...@@ -170,7 +170,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void {
170170
171 const allocator = builder.allocator;171 const allocator = builder.allocator;
172 for (builder.top_level_steps.toSliceConst()) |top_level_step| {172 for (builder.top_level_steps.toSliceConst()) |top_level_step| {
173 try out_stream.print(" {s22} {}\n", top_level_step.step.name, top_level_step.description);173 try out_stream.print(" {s:22} {}\n", top_level_step.step.name, top_level_step.description);
174 }174 }
175175
176 try out_stream.write(176 try out_stream.write(
...@@ -191,7 +191,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void {...@@ -191,7 +191,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void {
191 for (builder.available_options_list.toSliceConst()) |option| {191 for (builder.available_options_list.toSliceConst()) |option| {
192 const name = try fmt.allocPrint(allocator, " -D{}=[{}]", option.name, Builder.typeIdName(option.type_id));192 const name = try fmt.allocPrint(allocator, " -D{}=[{}]", option.name, Builder.typeIdName(option.type_id));
193 defer allocator.free(name);193 defer allocator.free(name);
194 try out_stream.print("{s24} {}\n", name, option.description);194 try out_stream.print("{s:24} {}\n", name, option.description);
195 }195 }
196 }196 }
197197
test/compare_output.zig+1-1
...@@ -122,7 +122,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {...@@ -122,7 +122,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
122 \\122 \\
123 \\pub fn main() void {123 \\pub fn main() void {
124 \\ const stdout = &(io.getStdOut() catch unreachable).outStream().stream;124 \\ const stdout = &(io.getStdOut() catch unreachable).outStream().stream;
125 \\ stdout.print("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable;125 \\ stdout.print("Hello, world!\n{d:4} {x:3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable;
126 \\}126 \\}
127 , "Hello, world!\n0012 012 a\n");127 , "Hello, world!\n0012 012 a\n");
128128