authorgravatar for benjamin.feng@glassdoor.comBenjamin Feng <benjamin.feng@glassdoor.com> 2020-02-29 11:56:21-06:00
committergravatar for benjamin.feng@glassdoor.comBenjamin Feng <benjamin.feng@glassdoor.com> 2020-03-12 08:58:36-05:00
logae3fb6df007807a37fe448fdef2b8ee669b29a5a
tree6840fb4207f11482a51cde24eefd8d481d57daf0
parent895f67cc6dfe3ade4b635c4c2168843b022edee7

Copy fmtstream


2 files changed, 1755 insertions(+), 0 deletions(-)

lib/std/fmtstream.zig created+1754
......@@ -0,0 +1,1754 @@
1const std = @import("std.zig");
2const math = std.math;
3const assert = std.debug.assert;
4const mem = std.mem;
5const builtin = @import("builtin");
6const errol = @import("fmt/errol.zig");
7const lossyCast = std.math.lossyCast;
8
9pub const default_max_depth = 3;
10
11pub const Alignment = enum {
12 Left,
13 Center,
14 Right,
15};
16
17pub const FormatOptions = struct {
18 precision: ?usize = null,
19 width: ?usize = null,
20 alignment: ?Alignment = null,
21 fill: u8 = ' ',
22};
23
24fn peekIsAlign(comptime fmt: []const u8) bool {
25 // Should only be called during a state transition to the format segment.
26 comptime assert(fmt[0] == ':');
27
28 inline for (([_]u8{ 1, 2 })[0..]) |i| {
29 if (fmt.len > i and (fmt[i] == '<' or fmt[i] == '^' or fmt[i] == '>')) {
30 return true;
31 }
32 }
33 return false;
34}
35
36/// Renders fmt string with args, calling output with slices of bytes.
37/// If `output` returns an error, the error is returned from `format` and
38/// `output` is not called again.
39///
40/// The format string must be comptime known and may contain placeholders following
41/// this format:
42/// `{[position][specifier]:[fill][alignment][width].[precision]}`
43///
44/// Each word between `[` and `]` is a parameter you have to replace with something:
45///
46/// - *position* is the index of the argument that should be inserted
47/// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below)
48/// - *fill* is a single character which is used to pad the formatted text
49/// - *alignment* is one of the three characters `<`, `^` or `>`. they define if the text is *left*, *center*, or *right* aligned
50/// - *width* is the total width of the field in characters
51/// - *precision* specifies how many decimals a formatted number should have
52///
53/// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when
54/// all parameters after the separator are omitted.
55/// Only exception is the *fill* parameter. If *fill* is required, one has to specify *alignment* as well, as otherwise
56/// the digits after `:` is interpreted as *width*, not *fill*.
57///
58/// The *specifier* has several options for types:
59/// - `x` and `X`:
60/// - format the non-numeric value as a string of bytes in hexadecimal notation ("binary dump") in either lower case or upper case
61/// - output numeric value in hexadecimal notation
62/// - `s`: print a pointer-to-many as a c-string, use zero-termination
63/// - `B` and `Bi`: output a memory size in either metric (1000) or power-of-two (1024) based notation. works for both float and integer values.
64/// - `e`: output floating point value in scientific notation
65/// - `d`: output numeric value in decimal notation
66/// - `b`: output integer value in binary notation
67/// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max.
68/// - `*`: output the address of the value instead of the value itself.
69///
70/// If a formatted user type contains a function of the type
71/// ```
72/// fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, context: var, comptime Errors: type, comptime output: fn (@TypeOf(context), []const u8) Errors!void) Errors!void
73/// ```
74/// with `?` being the type formatted, this function will be called instead of the default implementation.
75/// This allows user types to be formatted in a logical manner instead of dumping all fields of the type.
76///
77/// A user type may be a `struct`, `vector`, `union` or `enum` type.
78pub fn format(
79 context: var,
80 comptime Errors: type,
81 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
82 comptime fmt: []const u8,
83 args: var,
84) Errors!void {
85 const ArgSetType = u32;
86 if (@typeInfo(@TypeOf(args)) != .Struct) {
87 @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args)));
88 }
89 if (args.len > ArgSetType.bit_count) {
90 @compileError("32 arguments max are supported per format call");
91 }
92
93 const State = enum {
94 Start,
95 Positional,
96 CloseBrace,
97 Specifier,
98 FormatFillAndAlign,
99 FormatWidth,
100 FormatPrecision,
101 };
102
103 comptime var start_index = 0;
104 comptime var state = State.Start;
105 comptime var maybe_pos_arg: ?comptime_int = null;
106 comptime var specifier_start = 0;
107 comptime var specifier_end = 0;
108 comptime var options = FormatOptions{};
109 comptime var arg_state: struct {
110 next_arg: usize = 0,
111 used_args: ArgSetType = 0,
112 args_len: usize = args.len,
113
114 fn hasUnusedArgs(comptime self: *@This()) bool {
115 return (@popCount(ArgSetType, self.used_args) != self.args_len);
116 }
117
118 fn nextArg(comptime self: *@This(), comptime pos_arg: ?comptime_int) comptime_int {
119 const next_idx = pos_arg orelse blk: {
120 const arg = self.next_arg;
121 self.next_arg += 1;
122 break :blk arg;
123 };
124
125 if (next_idx >= self.args_len) {
126 @compileError("Too few arguments");
127 }
128
129 // Mark this argument as used
130 self.used_args |= 1 << next_idx;
131
132 return next_idx;
133 }
134 } = .{};
135
136 inline for (fmt) |c, i| {
137 switch (state) {
138 .Start => switch (c) {
139 '{' => {
140 if (start_index < i) {
141 try output(context, fmt[start_index..i]);
142 }
143
144 start_index = i;
145 specifier_start = i + 1;
146 specifier_end = i + 1;
147 maybe_pos_arg = null;
148 state = .Positional;
149 options = FormatOptions{};
150 },
151 '}' => {
152 if (start_index < i) {
153 try output(context, fmt[start_index..i]);
154 }
155 state = .CloseBrace;
156 },
157 else => {},
158 },
159 .Positional => switch (c) {
160 '{' => {
161 state = .Start;
162 start_index = i;
163 },
164 ':' => {
165 state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth;
166 specifier_end = i;
167 },
168 '0'...'9' => {
169 if (maybe_pos_arg == null) {
170 maybe_pos_arg = 0;
171 }
172
173 maybe_pos_arg.? *= 10;
174 maybe_pos_arg.? += c - '0';
175 specifier_start = i + 1;
176
177 if (maybe_pos_arg.? >= args.len) {
178 @compileError("Positional value refers to non-existent argument");
179 }
180 },
181 '}' => {
182 const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg);
183
184 try formatType(
185 args[arg_to_print],
186 fmt[0..0],
187 options,
188 context,
189 Errors,
190 output,
191 default_max_depth,
192 );
193
194 state = .Start;
195 start_index = i + 1;
196 },
197 else => {
198 state = .Specifier;
199 specifier_start = i;
200 },
201 },
202 .CloseBrace => switch (c) {
203 '}' => {
204 state = .Start;
205 start_index = i;
206 },
207 else => @compileError("Single '}' encountered in format string"),
208 },
209 .Specifier => switch (c) {
210 ':' => {
211 specifier_end = i;
212 state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth;
213 },
214 '}' => {
215 const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg);
216
217 try formatType(
218 args[arg_to_print],
219 fmt[specifier_start..i],
220 options,
221 context,
222 Errors,
223 output,
224 default_max_depth,
225 );
226 state = .Start;
227 start_index = i + 1;
228 },
229 else => {},
230 },
231 // Only entered if the format string contains a fill/align segment.
232 .FormatFillAndAlign => switch (c) {
233 '<' => {
234 options.alignment = Alignment.Left;
235 state = .FormatWidth;
236 },
237 '^' => {
238 options.alignment = Alignment.Center;
239 state = .FormatWidth;
240 },
241 '>' => {
242 options.alignment = Alignment.Right;
243 state = .FormatWidth;
244 },
245 else => {
246 options.fill = c;
247 },
248 },
249 .FormatWidth => switch (c) {
250 '0'...'9' => {
251 if (options.width == null) {
252 options.width = 0;
253 }
254
255 options.width.? *= 10;
256 options.width.? += c - '0';
257 },
258 '.' => {
259 state = .FormatPrecision;
260 },
261 '}' => {
262 const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg);
263
264 try formatType(
265 args[arg_to_print],
266 fmt[specifier_start..specifier_end],
267 options,
268 context,
269 Errors,
270 output,
271 default_max_depth,
272 );
273 state = .Start;
274 start_index = i + 1;
275 },
276 else => {
277 @compileError("Unexpected character in width value: " ++ [_]u8{c});
278 },
279 },
280 .FormatPrecision => switch (c) {
281 '0'...'9' => {
282 if (options.precision == null) {
283 options.precision = 0;
284 }
285
286 options.precision.? *= 10;
287 options.precision.? += c - '0';
288 },
289 '}' => {
290 const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg);
291
292 try formatType(
293 args[arg_to_print],
294 fmt[specifier_start..specifier_end],
295 options,
296 context,
297 Errors,
298 output,
299 default_max_depth,
300 );
301 state = .Start;
302 start_index = i + 1;
303 },
304 else => {
305 @compileError("Unexpected character in precision value: " ++ [_]u8{c});
306 },
307 },
308 }
309 }
310 comptime {
311 if (comptime arg_state.hasUnusedArgs()) {
312 @compileError("Unused arguments");
313 }
314 if (state != State.Start) {
315 @compileError("Incomplete format string: " ++ fmt);
316 }
317 }
318 if (start_index < fmt.len) {
319 try output(context, fmt[start_index..]);
320 }
321}
322
323pub fn formatType(
324 value: var,
325 comptime fmt: []const u8,
326 options: FormatOptions,
327 context: var,
328 comptime Errors: type,
329 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
330 max_depth: usize,
331) Errors!void {
332 if (comptime std.mem.eql(u8, fmt, "*")) {
333 try output(context, @typeName(@TypeOf(value).Child));
334 try output(context, "@");
335 try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, context, Errors, output);
336 return;
337 }
338
339 const T = @TypeOf(value);
340 switch (@typeInfo(T)) {
341 .ComptimeInt, .Int, .Float => {
342 return formatValue(value, fmt, options, context, Errors, output);
343 },
344 .Void => {
345 return output(context, "void");
346 },
347 .Bool => {
348 return output(context, if (value) "true" else "false");
349 },
350 .Optional => {
351 if (value) |payload| {
352 return formatType(payload, fmt, options, context, Errors, output, max_depth);
353 } else {
354 return output(context, "null");
355 }
356 },
357 .ErrorUnion => {
358 if (value) |payload| {
359 return formatType(payload, fmt, options, context, Errors, output, max_depth);
360 } else |err| {
361 return formatType(err, fmt, options, context, Errors, output, max_depth);
362 }
363 },
364 .ErrorSet => {
365 try output(context, "error.");
366 return output(context, @errorName(value));
367 },
368 .Enum => |enumInfo| {
369 if (comptime std.meta.trait.hasFn("format")(T)) {
370 return value.format(fmt, options, context, Errors, output);
371 }
372
373 try output(context, @typeName(T));
374 if (enumInfo.is_exhaustive) {
375 try output(context, ".");
376 try output(context, @tagName(value));
377 } else {
378 // TODO: when @tagName works on exhaustive enums print known enum strings
379 try output(context, "(");
380 try formatType(@enumToInt(value), fmt, options, context, Errors, output, max_depth);
381 try output(context, ")");
382 }
383 },
384 .Union => {
385 if (comptime std.meta.trait.hasFn("format")(T)) {
386 return value.format(fmt, options, context, Errors, output);
387 }
388
389 try output(context, @typeName(T));
390 if (max_depth == 0) {
391 return output(context, "{ ... }");
392 }
393 const info = @typeInfo(T).Union;
394 if (info.tag_type) |UnionTagType| {
395 try output(context, "{ .");
396 try output(context, @tagName(@as(UnionTagType, value)));
397 try output(context, " = ");
398 inline for (info.fields) |u_field| {
399 if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) {
400 try formatType(@field(value, u_field.name), fmt, options, context, Errors, output, max_depth - 1);
401 }
402 }
403 try output(context, " }");
404 } else {
405 try format(context, Errors, output, "@{x}", .{@ptrToInt(&value)});
406 }
407 },
408 .Struct => |StructT| {
409 if (comptime std.meta.trait.hasFn("format")(T)) {
410 return value.format(fmt, options, context, Errors, output);
411 }
412
413 try output(context, @typeName(T));
414 if (max_depth == 0) {
415 return output(context, "{ ... }");
416 }
417 try output(context, "{");
418 inline for (StructT.fields) |f, i| {
419 if (i == 0) {
420 try output(context, " .");
421 } else {
422 try output(context, ", .");
423 }
424 try output(context, f.name);
425 try output(context, " = ");
426 try formatType(@field(value, f.name), fmt, options, context, Errors, output, max_depth - 1);
427 }
428 try output(context, " }");
429 },
430 .Pointer => |ptr_info| switch (ptr_info.size) {
431 .One => switch (@typeInfo(ptr_info.child)) {
432 .Array => |info| {
433 if (info.child == u8) {
434 return formatText(value, fmt, options, context, Errors, output);
435 }
436 return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) });
437 },
438 .Enum, .Union, .Struct => {
439 return formatType(value.*, fmt, options, context, Errors, output, max_depth);
440 },
441 else => return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }),
442 },
443 .Many, .C => {
444 if (ptr_info.sentinel) |sentinel| {
445 return formatType(mem.span(value), fmt, options, context, Errors, output, max_depth);
446 }
447 if (ptr_info.child == u8) {
448 if (fmt.len > 0 and fmt[0] == 's') {
449 return formatText(mem.span(value), fmt, options, context, Errors, output);
450 }
451 }
452 return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) });
453 },
454 .Slice => {
455 if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) {
456 return formatText(value, fmt, options, context, Errors, output);
457 }
458 if (ptr_info.child == u8) {
459 return formatText(value, fmt, options, context, Errors, output);
460 }
461 return format(context, Errors, output, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) });
462 },
463 },
464 .Array => |info| {
465 const Slice = @Type(builtin.TypeInfo{
466 .Pointer = .{
467 .size = .Slice,
468 .is_const = true,
469 .is_volatile = false,
470 .is_allowzero = false,
471 .alignment = @alignOf(info.child),
472 .child = info.child,
473 .sentinel = null,
474 },
475 });
476 return formatType(@as(Slice, &value), fmt, options, context, Errors, output, max_depth);
477 },
478 .Vector => {
479 const len = @typeInfo(T).Vector.len;
480 try output(context, "{ ");
481 var i: usize = 0;
482 while (i < len) : (i += 1) {
483 try formatValue(value[i], fmt, options, context, Errors, output);
484 if (i < len - 1) {
485 try output(context, ", ");
486 }
487 }
488 try output(context, " }");
489 },
490 .Fn => {
491 return format(context, Errors, output, "{}@{x}", .{ @typeName(T), @ptrToInt(value) });
492 },
493 .Type => return output(context, @typeName(T)),
494 .EnumLiteral => {
495 const buffer = [_]u8{'.'} ++ @tagName(value);
496 return formatType(buffer, fmt, options, context, Errors, output, max_depth);
497 },
498 else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"),
499 }
500}
501
502fn formatValue(
503 value: var,
504 comptime fmt: []const u8,
505 options: FormatOptions,
506 context: var,
507 comptime Errors: type,
508 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
509) Errors!void {
510 if (comptime std.mem.eql(u8, fmt, "B")) {
511 return formatBytes(value, options, 1000, context, Errors, output);
512 } else if (comptime std.mem.eql(u8, fmt, "Bi")) {
513 return formatBytes(value, options, 1024, context, Errors, output);
514 }
515
516 const T = @TypeOf(value);
517 switch (@typeInfo(T)) {
518 .Float => return formatFloatValue(value, fmt, options, context, Errors, output),
519 .Int, .ComptimeInt => return formatIntValue(value, fmt, options, context, Errors, output),
520 .Bool => return output(context, if (value) "true" else "false"),
521 else => comptime unreachable,
522 }
523}
524
525pub fn formatIntValue(
526 value: var,
527 comptime fmt: []const u8,
528 options: FormatOptions,
529 context: var,
530 comptime Errors: type,
531 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
532) Errors!void {
533 comptime var radix = 10;
534 comptime var uppercase = false;
535
536 const int_value = if (@TypeOf(value) == comptime_int) blk: {
537 const Int = math.IntFittingRange(value, value);
538 break :blk @as(Int, value);
539 } else
540 value;
541
542 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) {
543 radix = 10;
544 uppercase = false;
545 } else if (comptime std.mem.eql(u8, fmt, "c")) {
546 if (@TypeOf(int_value).bit_count <= 8) {
547 return formatAsciiChar(@as(u8, int_value), options, context, Errors, output);
548 } else {
549 @compileError("Cannot print integer that is larger than 8 bits as a ascii");
550 }
551 } else if (comptime std.mem.eql(u8, fmt, "b")) {
552 radix = 2;
553 uppercase = false;
554 } else if (comptime std.mem.eql(u8, fmt, "x")) {
555 radix = 16;
556 uppercase = false;
557 } else if (comptime std.mem.eql(u8, fmt, "X")) {
558 radix = 16;
559 uppercase = true;
560 } else {
561 @compileError("Unknown format string: '" ++ fmt ++ "'");
562 }
563
564 return formatInt(int_value, radix, uppercase, options, context, Errors, output);
565}
566
567fn formatFloatValue(
568 value: var,
569 comptime fmt: []const u8,
570 options: FormatOptions,
571 context: var,
572 comptime Errors: type,
573 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
574) Errors!void {
575 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {
576 return formatFloatScientific(value, options, context, Errors, output);
577 } else if (comptime std.mem.eql(u8, fmt, "d")) {
578 return formatFloatDecimal(value, options, context, Errors, output);
579 } else {
580 @compileError("Unknown format string: '" ++ fmt ++ "'");
581 }
582}
583
584pub fn formatText(
585 bytes: []const u8,
586 comptime fmt: []const u8,
587 options: FormatOptions,
588 context: var,
589 comptime Errors: type,
590 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
591) Errors!void {
592 if (fmt.len == 0) {
593 return output(context, bytes);
594 } else if (comptime std.mem.eql(u8, fmt, "s")) {
595 return formatBuf(bytes, options, context, Errors, output);
596 } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) {
597 for (bytes) |c| {
598 try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, context, Errors, output);
599 }
600 return;
601 } else {
602 @compileError("Unknown format string: '" ++ fmt ++ "'");
603 }
604}
605
606pub fn formatAsciiChar(
607 c: u8,
608 options: FormatOptions,
609 context: var,
610 comptime Errors: type,
611 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
612) Errors!void {
613 return output(context, @as(*const [1]u8, &c)[0..]);
614}
615
616pub fn formatBuf(
617 buf: []const u8,
618 options: FormatOptions,
619 context: var,
620 comptime Errors: type,
621 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
622) Errors!void {
623 try output(context, buf);
624
625 const width = options.width orelse 0;
626 var leftover_padding = if (width > buf.len) (width - buf.len) else return;
627 const pad_byte: u8 = options.fill;
628 while (leftover_padding > 0) : (leftover_padding -= 1) {
629 try output(context, @as(*const [1]u8, &pad_byte)[0..1]);
630 }
631}
632
633// Print a float in scientific notation to the specified precision. Null uses full precision.
634// It should be the case that every full precision, printed value can be re-parsed back to the
635// same type unambiguously.
636pub fn formatFloatScientific(
637 value: var,
638 options: FormatOptions,
639 context: var,
640 comptime Errors: type,
641 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
642) Errors!void {
643 var x = @floatCast(f64, value);
644
645 // Errol doesn't handle these special cases.
646 if (math.signbit(x)) {
647 try output(context, "-");
648 x = -x;
649 }
650
651 if (math.isNan(x)) {
652 return output(context, "nan");
653 }
654 if (math.isPositiveInf(x)) {
655 return output(context, "inf");
656 }
657 if (x == 0.0) {
658 try output(context, "0");
659
660 if (options.precision) |precision| {
661 if (precision != 0) {
662 try output(context, ".");
663 var i: usize = 0;
664 while (i < precision) : (i += 1) {
665 try output(context, "0");
666 }
667 }
668 } else {
669 try output(context, ".0");
670 }
671
672 try output(context, "e+00");
673 return;
674 }
675
676 var buffer: [32]u8 = undefined;
677 var float_decimal = errol.errol3(x, buffer[0..]);
678
679 if (options.precision) |precision| {
680 errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific);
681
682 try output(context, float_decimal.digits[0..1]);
683
684 // {e0} case prints no `.`
685 if (precision != 0) {
686 try output(context, ".");
687
688 var printed: usize = 0;
689 if (float_decimal.digits.len > 1) {
690 const num_digits = math.min(float_decimal.digits.len, precision + 1);
691 try output(context, float_decimal.digits[1..num_digits]);
692 printed += num_digits - 1;
693 }
694
695 while (printed < precision) : (printed += 1) {
696 try output(context, "0");
697 }
698 }
699 } else {
700 try output(context, float_decimal.digits[0..1]);
701 try output(context, ".");
702 if (float_decimal.digits.len > 1) {
703 const num_digits = if (@TypeOf(value) == f32) math.min(@as(usize, 9), float_decimal.digits.len) else float_decimal.digits.len;
704
705 try output(context, float_decimal.digits[1..num_digits]);
706 } else {
707 try output(context, "0");
708 }
709 }
710
711 try output(context, "e");
712 const exp = float_decimal.exp - 1;
713
714 if (exp >= 0) {
715 try output(context, "+");
716 if (exp > -10 and exp < 10) {
717 try output(context, "0");
718 }
719 try formatInt(exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output);
720 } else {
721 try output(context, "-");
722 if (exp > -10 and exp < 10) {
723 try output(context, "0");
724 }
725 try formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output);
726 }
727}
728
729// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
730// By default floats are printed at full precision (no rounding).
731pub fn formatFloatDecimal(
732 value: var,
733 options: FormatOptions,
734 context: var,
735 comptime Errors: type,
736 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
737) Errors!void {
738 var x = @as(f64, value);
739
740 // Errol doesn't handle these special cases.
741 if (math.signbit(x)) {
742 try output(context, "-");
743 x = -x;
744 }
745
746 if (math.isNan(x)) {
747 return output(context, "nan");
748 }
749 if (math.isPositiveInf(x)) {
750 return output(context, "inf");
751 }
752 if (x == 0.0) {
753 try output(context, "0");
754
755 if (options.precision) |precision| {
756 if (precision != 0) {
757 try output(context, ".");
758 var i: usize = 0;
759 while (i < precision) : (i += 1) {
760 try output(context, "0");
761 }
762 } else {
763 try output(context, ".0");
764 }
765 }
766
767 return;
768 }
769
770 // non-special case, use errol3
771 var buffer: [32]u8 = undefined;
772 var float_decimal = errol.errol3(x, buffer[0..]);
773
774 if (options.precision) |precision| {
775 errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal);
776
777 // exp < 0 means the leading is always 0 as errol result is normalized.
778 var num_digits_whole = if (float_decimal.exp > 0) @intCast(usize, float_decimal.exp) else 0;
779
780 // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
781 var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
782
783 if (num_digits_whole > 0) {
784 // We may have to zero pad, for instance 1e4 requires zero padding.
785 try output(context, float_decimal.digits[0..num_digits_whole_no_pad]);
786
787 var i = num_digits_whole_no_pad;
788 while (i < num_digits_whole) : (i += 1) {
789 try output(context, "0");
790 }
791 } else {
792 try output(context, "0");
793 }
794
795 // {.0} special case doesn't want a trailing '.'
796 if (precision == 0) {
797 return;
798 }
799
800 try output(context, ".");
801
802 // Keep track of fractional count printed for case where we pre-pad then post-pad with 0's.
803 var printed: usize = 0;
804
805 // Zero-fill until we reach significant digits or run out of precision.
806 if (float_decimal.exp <= 0) {
807 const zero_digit_count = @intCast(usize, -float_decimal.exp);
808 const zeros_to_print = math.min(zero_digit_count, precision);
809
810 var i: usize = 0;
811 while (i < zeros_to_print) : (i += 1) {
812 try output(context, "0");
813 printed += 1;
814 }
815
816 if (printed >= precision) {
817 return;
818 }
819 }
820
821 // Remaining fractional portion, zero-padding if insufficient.
822 assert(precision >= printed);
823 if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) {
824 try output(context, float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]);
825 return;
826 } else {
827 try output(context, float_decimal.digits[num_digits_whole_no_pad..]);
828 printed += float_decimal.digits.len - num_digits_whole_no_pad;
829
830 while (printed < precision) : (printed += 1) {
831 try output(context, "0");
832 }
833 }
834 } else {
835 // exp < 0 means the leading is always 0 as errol result is normalized.
836 var num_digits_whole = if (float_decimal.exp > 0) @intCast(usize, float_decimal.exp) else 0;
837
838 // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
839 var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
840
841 if (num_digits_whole > 0) {
842 // We may have to zero pad, for instance 1e4 requires zero padding.
843 try output(context, float_decimal.digits[0..num_digits_whole_no_pad]);
844
845 var i = num_digits_whole_no_pad;
846 while (i < num_digits_whole) : (i += 1) {
847 try output(context, "0");
848 }
849 } else {
850 try output(context, "0");
851 }
852
853 // Omit `.` if no fractional portion
854 if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) {
855 return;
856 }
857
858 try output(context, ".");
859
860 // Zero-fill until we reach significant digits or run out of precision.
861 if (float_decimal.exp < 0) {
862 const zero_digit_count = @intCast(usize, -float_decimal.exp);
863
864 var i: usize = 0;
865 while (i < zero_digit_count) : (i += 1) {
866 try output(context, "0");
867 }
868 }
869
870 try output(context, float_decimal.digits[num_digits_whole_no_pad..]);
871 }
872}
873
874pub fn formatBytes(
875 value: var,
876 options: FormatOptions,
877 comptime radix: usize,
878 context: var,
879 comptime Errors: type,
880 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
881) Errors!void {
882 if (value == 0) {
883 return output(context, "0B");
884 }
885
886 const mags_si = " kMGTPEZY";
887 const mags_iec = " KMGTPEZY";
888 const magnitude = switch (radix) {
889 1000 => math.min(math.log2(value) / comptime math.log2(1000), mags_si.len - 1),
890 1024 => math.min(math.log2(value) / 10, mags_iec.len - 1),
891 else => unreachable,
892 };
893 const new_value = lossyCast(f64, value) / math.pow(f64, lossyCast(f64, radix), lossyCast(f64, magnitude));
894 const suffix = switch (radix) {
895 1000 => mags_si[magnitude],
896 1024 => mags_iec[magnitude],
897 else => unreachable,
898 };
899
900 try formatFloatDecimal(new_value, options, context, Errors, output);
901
902 if (suffix == ' ') {
903 return output(context, "B");
904 }
905
906 const buf = switch (radix) {
907 1000 => &[_]u8{ suffix, 'B' },
908 1024 => &[_]u8{ suffix, 'i', 'B' },
909 else => unreachable,
910 };
911 return output(context, buf);
912}
913
914pub fn formatInt(
915 value: var,
916 base: u8,
917 uppercase: bool,
918 options: FormatOptions,
919 context: var,
920 comptime Errors: type,
921 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
922) Errors!void {
923 const int_value = if (@TypeOf(value) == comptime_int) blk: {
924 const Int = math.IntFittingRange(value, value);
925 break :blk @as(Int, value);
926 } else
927 value;
928
929 if (@TypeOf(int_value).is_signed) {
930 return formatIntSigned(int_value, base, uppercase, options, context, Errors, output);
931 } else {
932 return formatIntUnsigned(int_value, base, uppercase, options, context, Errors, output);
933 }
934}
935
936fn formatIntSigned(
937 value: var,
938 base: u8,
939 uppercase: bool,
940 options: FormatOptions,
941 context: var,
942 comptime Errors: type,
943 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
944) Errors!void {
945 const new_options = FormatOptions{
946 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
947 .precision = options.precision,
948 .fill = options.fill,
949 };
950 const bit_count = @typeInfo(@TypeOf(value)).Int.bits;
951 const Uint = std.meta.IntType(false, bit_count);
952 if (value < 0) {
953 try output(context, "-");
954 const new_value = math.absCast(value);
955 return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output);
956 } else if (options.width == null or options.width.? == 0) {
957 return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, context, Errors, output);
958 } else {
959 try output(context, "+");
960 const new_value = @intCast(Uint, value);
961 return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output);
962 }
963}
964
965fn formatIntUnsigned(
966 value: var,
967 base: u8,
968 uppercase: bool,
969 options: FormatOptions,
970 context: var,
971 comptime Errors: type,
972 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
973) Errors!void {
974 assert(base >= 2);
975 var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined;
976 const min_int_bits = comptime math.max(@TypeOf(value).bit_count, @TypeOf(base).bit_count);
977 const MinInt = std.meta.IntType(@TypeOf(value).is_signed, min_int_bits);
978 var a: MinInt = value;
979 var index: usize = buf.len;
980
981 while (true) {
982 const digit = a % base;
983 index -= 1;
984 buf[index] = digitToChar(@intCast(u8, digit), uppercase);
985 a /= base;
986 if (a == 0) break;
987 }
988
989 const digits_buf = buf[index..];
990 const width = options.width orelse 0;
991 const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0;
992
993 if (padding > index) {
994 const zero_byte: u8 = options.fill;
995 var leftover_padding = padding - index;
996 while (true) {
997 try output(context, @as(*const [1]u8, &zero_byte)[0..]);
998 leftover_padding -= 1;
999 if (leftover_padding == 0) break;
1000 }
1001 mem.set(u8, buf[0..index], options.fill);
1002 return output(context, &buf);
1003 } else {
1004 const padded_buf = buf[index - padding ..];
1005 mem.set(u8, padded_buf[0..padding], options.fill);
1006 return output(context, padded_buf);
1007 }
1008}
1009
1010pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize {
1011 var context = FormatIntBuf{
1012 .out_buf = out_buf,
1013 .index = 0,
1014 };
1015 formatInt(value, base, uppercase, options, &context, error{}, formatIntCallback) catch unreachable;
1016 return context.index;
1017}
1018const FormatIntBuf = struct {
1019 out_buf: []u8,
1020 index: usize,
1021};
1022fn formatIntCallback(context: *FormatIntBuf, bytes: []const u8) (error{}!void) {
1023 mem.copy(u8, context.out_buf[context.index..], bytes);
1024 context.index += bytes.len;
1025}
1026
1027pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T {
1028 if (!T.is_signed) return parseUnsigned(T, buf, radix);
1029 if (buf.len == 0) return @as(T, 0);
1030 if (buf[0] == '-') {
1031 return math.negate(try parseUnsigned(T, buf[1..], radix));
1032 } else if (buf[0] == '+') {
1033 return parseUnsigned(T, buf[1..], radix);
1034 } else {
1035 return parseUnsigned(T, buf, radix);
1036 }
1037}
1038
1039test "parseInt" {
1040 std.testing.expect((parseInt(i32, "-10", 10) catch unreachable) == -10);
1041 std.testing.expect((parseInt(i32, "+10", 10) catch unreachable) == 10);
1042 std.testing.expect(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidCharacter);
1043 std.testing.expect(if (parseInt(i32, "10 ", 10)) |_| false else |err| err == error.InvalidCharacter);
1044 std.testing.expect(if (parseInt(u32, "-10", 10)) |_| false else |err| err == error.InvalidCharacter);
1045 std.testing.expect((parseInt(u8, "255", 10) catch unreachable) == 255);
1046 std.testing.expect(if (parseInt(u8, "256", 10)) |_| false else |err| err == error.Overflow);
1047}
1048
1049pub const ParseUnsignedError = error{
1050 /// The result cannot fit in the type specified
1051 Overflow,
1052
1053 /// The input had a byte that was not a digit
1054 InvalidCharacter,
1055};
1056
1057pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsignedError!T {
1058 var x: T = 0;
1059
1060 for (buf) |c| {
1061 const digit = try charToDigit(c, radix);
1062
1063 if (x != 0) x = try math.mul(T, x, try math.cast(T, radix));
1064 x = try math.add(T, x, try math.cast(T, digit));
1065 }
1066
1067 return x;
1068}
1069
1070test "parseUnsigned" {
1071 std.testing.expect((try parseUnsigned(u16, "050124", 10)) == 50124);
1072 std.testing.expect((try parseUnsigned(u16, "65535", 10)) == 65535);
1073 std.testing.expectError(error.Overflow, parseUnsigned(u16, "65536", 10));
1074
1075 std.testing.expect((try parseUnsigned(u64, "0ffffffffffffffff", 16)) == 0xffffffffffffffff);
1076 std.testing.expectError(error.Overflow, parseUnsigned(u64, "10000000000000000", 16));
1077
1078 std.testing.expect((try parseUnsigned(u32, "DeadBeef", 16)) == 0xDEADBEEF);
1079
1080 std.testing.expect((try parseUnsigned(u7, "1", 10)) == 1);
1081 std.testing.expect((try parseUnsigned(u7, "1000", 2)) == 8);
1082
1083 std.testing.expectError(error.InvalidCharacter, parseUnsigned(u32, "f", 10));
1084 std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "109", 8));
1085
1086 std.testing.expect((try parseUnsigned(u32, "NUMBER", 36)) == 1442151747);
1087
1088 // these numbers should fit even though the radix itself doesn't fit in the destination type
1089 std.testing.expect((try parseUnsigned(u1, "0", 10)) == 0);
1090 std.testing.expect((try parseUnsigned(u1, "1", 10)) == 1);
1091 std.testing.expectError(error.Overflow, parseUnsigned(u1, "2", 10));
1092 std.testing.expect((try parseUnsigned(u1, "001", 16)) == 1);
1093 std.testing.expect((try parseUnsigned(u2, "3", 16)) == 3);
1094 std.testing.expectError(error.Overflow, parseUnsigned(u2, "4", 16));
1095}
1096
1097pub const parseFloat = @import("fmt/parse_float.zig").parseFloat;
1098
1099test "parseFloat" {
1100 _ = @import("fmt/parse_float.zig");
1101}
1102
1103pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) {
1104 const value = switch (c) {
1105 '0'...'9' => c - '0',
1106 'A'...'Z' => c - 'A' + 10,
1107 'a'...'z' => c - 'a' + 10,
1108 else => return error.InvalidCharacter,
1109 };
1110
1111 if (value >= radix) return error.InvalidCharacter;
1112
1113 return value;
1114}
1115
1116fn digitToChar(digit: u8, uppercase: bool) u8 {
1117 return switch (digit) {
1118 0...9 => digit + '0',
1119 10...35 => digit + ((if (uppercase) @as(u8, 'A') else @as(u8, 'a')) - 10),
1120 else => unreachable,
1121 };
1122}
1123
1124const BufPrintContext = struct {
1125 remaining: []u8,
1126};
1127
1128fn bufPrintWrite(context: *BufPrintContext, bytes: []const u8) !void {
1129 if (context.remaining.len < bytes.len) {
1130 mem.copy(u8, context.remaining, bytes[0..context.remaining.len]);
1131 return error.BufferTooSmall;
1132 }
1133 mem.copy(u8, context.remaining, bytes);
1134 context.remaining = context.remaining[bytes.len..];
1135}
1136
1137pub const BufPrintError = error{
1138 /// As much as possible was written to the buffer, but it was too small to fit all the printed bytes.
1139 BufferTooSmall,
1140};
1141pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: var) BufPrintError![]u8 {
1142 var context = BufPrintContext{ .remaining = buf };
1143 try format(&context, BufPrintError, bufPrintWrite, fmt, args);
1144 return buf[0 .. buf.len - context.remaining.len];
1145}
1146
1147pub const AllocPrintError = error{OutOfMemory};
1148
1149pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![]u8 {
1150 var size: usize = 0;
1151 format(&size, error{}, countSize, fmt, args) catch |err| switch (err) {};
1152 const buf = try allocator.alloc(u8, size);
1153 return bufPrint(buf, fmt, args) catch |err| switch (err) {
1154 error.BufferTooSmall => unreachable, // we just counted the size above
1155 };
1156}
1157
1158fn countSize(size: *usize, bytes: []const u8) (error{}!void) {
1159 size.* += bytes.len;
1160}
1161
1162pub fn allocPrint0(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![:0]u8 {
1163 const result = try allocPrint(allocator, fmt ++ "\x00", args);
1164 return result[0 .. result.len - 1 :0];
1165}
1166
1167test "bufPrintInt" {
1168 var buffer: [100]u8 = undefined;
1169 const buf = buffer[0..];
1170
1171 std.testing.expectEqualSlices(u8, "-1", bufPrintIntToSlice(buf, @as(i1, -1), 10, false, FormatOptions{}));
1172
1173 std.testing.expectEqualSlices(u8, "-101111000110000101001110", bufPrintIntToSlice(buf, @as(i32, -12345678), 2, false, FormatOptions{}));
1174 std.testing.expectEqualSlices(u8, "-12345678", bufPrintIntToSlice(buf, @as(i32, -12345678), 10, false, FormatOptions{}));
1175 std.testing.expectEqualSlices(u8, "-bc614e", bufPrintIntToSlice(buf, @as(i32, -12345678), 16, false, FormatOptions{}));
1176 std.testing.expectEqualSlices(u8, "-BC614E", bufPrintIntToSlice(buf, @as(i32, -12345678), 16, true, FormatOptions{}));
1177
1178 std.testing.expectEqualSlices(u8, "12345678", bufPrintIntToSlice(buf, @as(u32, 12345678), 10, true, FormatOptions{}));
1179
1180 std.testing.expectEqualSlices(u8, " 666", bufPrintIntToSlice(buf, @as(u32, 666), 10, false, FormatOptions{ .width = 6 }));
1181 std.testing.expectEqualSlices(u8, " 1234", bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 6 }));
1182 std.testing.expectEqualSlices(u8, "1234", bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 1 }));
1183
1184 std.testing.expectEqualSlices(u8, "+42", bufPrintIntToSlice(buf, @as(i32, 42), 10, false, FormatOptions{ .width = 3 }));
1185 std.testing.expectEqualSlices(u8, "-42", bufPrintIntToSlice(buf, @as(i32, -42), 10, false, FormatOptions{ .width = 3 }));
1186}
1187
1188fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) []u8 {
1189 return buf[0..formatIntBuf(buf, value, base, uppercase, options)];
1190}
1191
1192test "parse u64 digit too big" {
1193 _ = parseUnsigned(u64, "123a", 10) catch |err| {
1194 if (err == error.InvalidCharacter) return;
1195 unreachable;
1196 };
1197 unreachable;
1198}
1199
1200test "parse unsigned comptime" {
1201 comptime {
1202 std.testing.expect((try parseUnsigned(usize, "2", 10)) == 2);
1203 }
1204}
1205
1206test "optional" {
1207 {
1208 const value: ?i32 = 1234;
1209 try testFmt("optional: 1234\n", "optional: {}\n", .{value});
1210 }
1211 {
1212 const value: ?i32 = null;
1213 try testFmt("optional: null\n", "optional: {}\n", .{value});
1214 }
1215}
1216
1217test "error" {
1218 {
1219 const value: anyerror!i32 = 1234;
1220 try testFmt("error union: 1234\n", "error union: {}\n", .{value});
1221 }
1222 {
1223 const value: anyerror!i32 = error.InvalidChar;
1224 try testFmt("error union: error.InvalidChar\n", "error union: {}\n", .{value});
1225 }
1226}
1227
1228test "int.small" {
1229 {
1230 const value: u3 = 0b101;
1231 try testFmt("u3: 5\n", "u3: {}\n", .{value});
1232 }
1233}
1234
1235test "int.specifier" {
1236 {
1237 const value: u8 = 'a';
1238 try testFmt("u8: a\n", "u8: {c}\n", .{value});
1239 }
1240 {
1241 const value: u8 = 0b1100;
1242 try testFmt("u8: 0b1100\n", "u8: 0b{b}\n", .{value});
1243 }
1244}
1245
1246test "int.padded" {
1247 try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)});
1248 try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)});
1249}
1250
1251test "buffer" {
1252 {
1253 var buf1: [32]u8 = undefined;
1254 var context = BufPrintContext{ .remaining = buf1[0..] };
1255 try formatType(1234, "", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
1256 var res = buf1[0 .. buf1.len - context.remaining.len];
1257 std.testing.expect(mem.eql(u8, res, "1234"));
1258
1259 context = BufPrintContext{ .remaining = buf1[0..] };
1260 try formatType('a', "c", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
1261 res = buf1[0 .. buf1.len - context.remaining.len];
1262 std.testing.expect(mem.eql(u8, res, "a"));
1263
1264 context = BufPrintContext{ .remaining = buf1[0..] };
1265 try formatType(0b1100, "b", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth);
1266 res = buf1[0 .. buf1.len - context.remaining.len];
1267 std.testing.expect(mem.eql(u8, res, "1100"));
1268 }
1269}
1270
1271test "array" {
1272 {
1273 const value: [3]u8 = "abc".*;
1274 try testFmt("array: abc\n", "array: {}\n", .{value});
1275 try testFmt("array: abc\n", "array: {}\n", .{&value});
1276
1277 var buf: [100]u8 = undefined;
1278 try testFmt(
1279 try bufPrint(buf[0..], "array: [3]u8@{x}\n", .{@ptrToInt(&value)}),
1280 "array: {*}\n",
1281 .{&value},
1282 );
1283 }
1284}
1285
1286test "slice" {
1287 {
1288 const value: []const u8 = "abc";
1289 try testFmt("slice: abc\n", "slice: {}\n", .{value});
1290 }
1291 {
1292 const value = @intToPtr([*]align(1) const []const u8, 0xdeadbeef)[0..0];
1293 try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value});
1294 }
1295
1296 try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"});
1297 try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"});
1298}
1299
1300test "pointer" {
1301 {
1302 const value = @intToPtr(*align(1) i32, 0xdeadbeef);
1303 try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", .{value});
1304 try testFmt("pointer: i32@deadbeef\n", "pointer: {*}\n", .{value});
1305 }
1306 {
1307 const value = @intToPtr(fn () void, 0xdeadbeef);
1308 try testFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", .{value});
1309 }
1310 {
1311 const value = @intToPtr(fn () void, 0xdeadbeef);
1312 try testFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", .{value});
1313 }
1314}
1315
1316test "cstr" {
1317 try testFmt(
1318 "cstr: Test C\n",
1319 "cstr: {s}\n",
1320 .{@ptrCast([*c]const u8, "Test C")},
1321 );
1322 try testFmt(
1323 "cstr: Test C \n",
1324 "cstr: {s:10}\n",
1325 .{@ptrCast([*c]const u8, "Test C")},
1326 );
1327}
1328
1329test "filesize" {
1330 try testFmt("file size: 63MiB\n", "file size: {Bi}\n", .{@as(usize, 63 * 1024 * 1024)});
1331 try testFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{@as(usize, 63 * 1024 * 1024)});
1332}
1333
1334test "struct" {
1335 {
1336 const Struct = struct {
1337 field: u8,
1338 };
1339 const value = Struct{ .field = 42 };
1340 try testFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{value});
1341 try testFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{&value});
1342 }
1343 {
1344 const Struct = struct {
1345 a: u0,
1346 b: u1,
1347 };
1348 const value = Struct{ .a = 0, .b = 1 };
1349 try testFmt("struct: Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", .{value});
1350 }
1351}
1352
1353test "enum" {
1354 const Enum = enum {
1355 One,
1356 Two,
1357 };
1358 const value = Enum.Two;
1359 try testFmt("enum: Enum.Two\n", "enum: {}\n", .{value});
1360 try testFmt("enum: Enum.Two\n", "enum: {}\n", .{&value});
1361}
1362
1363test "non-exhaustive enum" {
1364 const Enum = enum(u16) {
1365 One = 0x000f,
1366 Two = 0xbeef,
1367 _,
1368 };
1369 try testFmt("enum: Enum(15)\n", "enum: {}\n", .{Enum.One});
1370 try testFmt("enum: Enum(48879)\n", "enum: {}\n", .{Enum.Two});
1371 try testFmt("enum: Enum(4660)\n", "enum: {}\n", .{@intToEnum(Enum, 0x1234)});
1372 try testFmt("enum: Enum(f)\n", "enum: {x}\n", .{Enum.One});
1373 try testFmt("enum: Enum(beef)\n", "enum: {x}\n", .{Enum.Two});
1374 try testFmt("enum: Enum(1234)\n", "enum: {x}\n", .{@intToEnum(Enum, 0x1234)});
1375}
1376
1377test "float.scientific" {
1378 try testFmt("f32: 1.34000003e+00", "f32: {e}", .{@as(f32, 1.34)});
1379 try testFmt("f32: 1.23400001e+01", "f32: {e}", .{@as(f32, 12.34)});
1380 try testFmt("f64: -1.234e+11", "f64: {e}", .{@as(f64, -12.34e10)});
1381 try testFmt("f64: 9.99996e-40", "f64: {e}", .{@as(f64, 9.999960e-40)});
1382}
1383
1384test "float.scientific.precision" {
1385 try testFmt("f64: 1.40971e-42", "f64: {e:.5}", .{@as(f64, 1.409706e-42)});
1386 try testFmt("f64: 1.00000e-09", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 814313563)))});
1387 try testFmt("f64: 7.81250e-03", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1006632960)))});
1388 // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05.
1389 // In fact, libc doesn't round a lot of 5 cases up when one past the precision point.
1390 try testFmt("f64: 1.00001e+05", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1203982400)))});
1391}
1392
1393test "float.special" {
1394 try testFmt("f64: nan", "f64: {}", .{math.nan_f64});
1395 // negative nan is not defined by IEE 754,
1396 // and ARM thus normalizes it to positive nan
1397 if (builtin.arch != builtin.Arch.arm) {
1398 try testFmt("f64: -nan", "f64: {}", .{-math.nan_f64});
1399 }
1400 try testFmt("f64: inf", "f64: {}", .{math.inf_f64});
1401 try testFmt("f64: -inf", "f64: {}", .{-math.inf_f64});
1402}
1403
1404test "float.decimal" {
1405 try testFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)});
1406 try testFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)});
1407 try testFmt("f32: 1.1", "f32: {d:.1}", .{@as(f32, 1.1234)});
1408 try testFmt("f32: 1234.57", "f32: {d:.2}", .{@as(f32, 1234.567)});
1409 // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
1410 // -11.12339... is rounded back up to -11.1234
1411 try testFmt("f32: -11.1234", "f32: {d:.4}", .{@as(f32, -11.1234)});
1412 try testFmt("f32: 91.12345", "f32: {d:.5}", .{@as(f32, 91.12345)});
1413 try testFmt("f64: 91.1234567890", "f64: {d:.10}", .{@as(f64, 91.12345678901235)});
1414 try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 0.0)});
1415 try testFmt("f64: 6", "f64: {d:.0}", .{@as(f64, 5.700)});
1416 try testFmt("f64: 10.0", "f64: {d:.1}", .{@as(f64, 9.999)});
1417 try testFmt("f64: 1.000", "f64: {d:.3}", .{@as(f64, 1.0)});
1418 try testFmt("f64: 0.00030000", "f64: {d:.8}", .{@as(f64, 0.0003)});
1419 try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 1.40130e-45)});
1420 try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 9.999960e-40)});
1421}
1422
1423test "float.libc.sanity" {
1424 try testFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 916964781)))});
1425 try testFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 925353389)))});
1426 try testFmt("f64: 0.10000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1036831278)))});
1427 try testFmt("f64: 1.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1065353133)))});
1428 try testFmt("f64: 10.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1092616192)))});
1429
1430 // libc differences
1431 //
1432 // This is 0.015625 exactly according to gdb. We thus round down,
1433 // however glibc rounds up for some reason. This occurs for all
1434 // floats of the form x.yyyy25 on a precision point.
1435 try testFmt("f64: 0.01563", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1015021568)))});
1436 // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3
1437 // also rounds to 630 so I'm inclined to believe libc is not
1438 // optimal here.
1439 try testFmt("f64: 18014400656965630.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1518338049)))});
1440}
1441
1442test "custom" {
1443 const Vec2 = struct {
1444 const SelfType = @This();
1445 x: f32,
1446 y: f32,
1447
1448 pub fn format(
1449 self: SelfType,
1450 comptime fmt: []const u8,
1451 options: FormatOptions,
1452 context: var,
1453 comptime Errors: type,
1454 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
1455 ) Errors!void {
1456 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) {
1457 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
1458 } else if (comptime std.mem.eql(u8, fmt, "d")) {
1459 return std.fmt.format(context, Errors, output, "{d:.3}x{d:.3}", .{ self.x, self.y });
1460 } else {
1461 @compileError("Unknown format character: '" ++ fmt ++ "'");
1462 }
1463 }
1464 };
1465
1466 var buf1: [32]u8 = undefined;
1467 var value = Vec2{
1468 .x = 10.2,
1469 .y = 2.22,
1470 };
1471 try testFmt("point: (10.200,2.220)\n", "point: {}\n", .{&value});
1472 try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{&value});
1473
1474 // same thing but not passing a pointer
1475 try testFmt("point: (10.200,2.220)\n", "point: {}\n", .{value});
1476 try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{value});
1477}
1478
1479test "struct" {
1480 const S = struct {
1481 a: u32,
1482 b: anyerror,
1483 };
1484
1485 const inst = S{
1486 .a = 456,
1487 .b = error.Unused,
1488 };
1489
1490 try testFmt("S{ .a = 456, .b = error.Unused }", "{}", .{inst});
1491}
1492
1493test "union" {
1494 const TU = union(enum) {
1495 float: f32,
1496 int: u32,
1497 };
1498
1499 const UU = union {
1500 float: f32,
1501 int: u32,
1502 };
1503
1504 const EU = extern union {
1505 float: f32,
1506 int: u32,
1507 };
1508
1509 const tu_inst = TU{ .int = 123 };
1510 const uu_inst = UU{ .int = 456 };
1511 const eu_inst = EU{ .float = 321.123 };
1512
1513 try testFmt("TU{ .int = 123 }", "{}", .{tu_inst});
1514
1515 var buf: [100]u8 = undefined;
1516 const uu_result = try bufPrint(buf[0..], "{}", .{uu_inst});
1517 std.testing.expect(mem.eql(u8, uu_result[0..3], "UU@"));
1518
1519 const eu_result = try bufPrint(buf[0..], "{}", .{eu_inst});
1520 std.testing.expect(mem.eql(u8, uu_result[0..3], "EU@"));
1521}
1522
1523test "enum" {
1524 const E = enum {
1525 One,
1526 Two,
1527 Three,
1528 };
1529
1530 const inst = E.Two;
1531
1532 try testFmt("E.Two", "{}", .{inst});
1533}
1534
1535test "struct.self-referential" {
1536 const S = struct {
1537 const SelfType = @This();
1538 a: ?*SelfType,
1539 };
1540
1541 var inst = S{
1542 .a = null,
1543 };
1544 inst.a = &inst;
1545
1546 try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", .{inst});
1547}
1548
1549test "struct.zero-size" {
1550 const A = struct {
1551 fn foo() void {}
1552 };
1553 const B = struct {
1554 a: A,
1555 c: i32,
1556 };
1557
1558 const a = A{};
1559 const b = B{ .a = a, .c = 0 };
1560
1561 try testFmt("B{ .a = A{ }, .c = 0 }", "{}", .{b});
1562}
1563
1564test "bytes.hex" {
1565 const some_bytes = "\xCA\xFE\xBA\xBE";
1566 try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes});
1567 try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes});
1568 //Test Slices
1569 try testFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]});
1570 try testFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]});
1571 const bytes_with_zeros = "\x00\x0E\xBA\xBE";
1572 try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros});
1573}
1574
1575fn testFmt(expected: []const u8, comptime template: []const u8, args: var) !void {
1576 var buf: [100]u8 = undefined;
1577 const result = try bufPrint(buf[0..], template, args);
1578 if (mem.eql(u8, result, expected)) return;
1579
1580 std.debug.warn("\n====== expected this output: =========\n", .{});
1581 std.debug.warn("{}", .{expected});
1582 std.debug.warn("\n======== instead found this: =========\n", .{});
1583 std.debug.warn("{}", .{result});
1584 std.debug.warn("\n======================================\n", .{});
1585 return error.TestFailed;
1586}
1587
1588pub fn trim(buf: []const u8) []const u8 {
1589 var start: usize = 0;
1590 while (start < buf.len and isWhiteSpace(buf[start])) : (start += 1) {}
1591
1592 var end: usize = buf.len;
1593 while (true) {
1594 if (end > start) {
1595 const new_end = end - 1;
1596 if (isWhiteSpace(buf[new_end])) {
1597 end = new_end;
1598 continue;
1599 }
1600 }
1601 break;
1602 }
1603 return buf[start..end];
1604}
1605
1606test "trim" {
1607 std.testing.expect(mem.eql(u8, "abc", trim("\n abc \t")));
1608 std.testing.expect(mem.eql(u8, "", trim(" ")));
1609 std.testing.expect(mem.eql(u8, "", trim("")));
1610 std.testing.expect(mem.eql(u8, "abc", trim(" abc")));
1611 std.testing.expect(mem.eql(u8, "abc", trim("abc ")));
1612}
1613
1614pub fn isWhiteSpace(byte: u8) bool {
1615 return switch (byte) {
1616 ' ', '\t', '\n', '\r' => true,
1617 else => false,
1618 };
1619}
1620
1621pub fn hexToBytes(out: []u8, input: []const u8) !void {
1622 if (out.len * 2 < input.len)
1623 return error.InvalidLength;
1624
1625 var in_i: usize = 0;
1626 while (in_i != input.len) : (in_i += 2) {
1627 const hi = try charToDigit(input[in_i], 16);
1628 const lo = try charToDigit(input[in_i + 1], 16);
1629 out[in_i / 2] = (hi << 4) | lo;
1630 }
1631}
1632
1633test "hexToBytes" {
1634 const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706";
1635 var pb: [32]u8 = undefined;
1636 try hexToBytes(pb[0..], test_hex_str);
1637 try testFmt(test_hex_str, "{X}", .{pb});
1638}
1639
1640test "formatIntValue with comptime_int" {
1641 const value: comptime_int = 123456789123456789;
1642
1643 var buf = std.ArrayList(u8).init(std.testing.allocator);
1644 defer buf.deinit();
1645 try formatIntValue(value, "", FormatOptions{}, &buf, @TypeOf(std.ArrayList(u8).appendSlice).ReturnType.ErrorSet, std.ArrayList(u8).appendSlice);
1646 std.testing.expect(mem.eql(u8, buf.toSliceConst(), "123456789123456789"));
1647}
1648
1649test "formatType max_depth" {
1650 const Vec2 = struct {
1651 const SelfType = @This();
1652 x: f32,
1653 y: f32,
1654
1655 pub fn format(
1656 self: SelfType,
1657 comptime fmt: []const u8,
1658 options: FormatOptions,
1659 context: var,
1660 comptime Errors: type,
1661 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
1662 ) Errors!void {
1663 if (fmt.len == 0) {
1664 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
1665 } else {
1666 @compileError("Unknown format string: '" ++ fmt ++ "'");
1667 }
1668 }
1669 };
1670 const E = enum {
1671 One,
1672 Two,
1673 Three,
1674 };
1675 const TU = union(enum) {
1676 const SelfType = @This();
1677 float: f32,
1678 int: u32,
1679 ptr: ?*SelfType,
1680 };
1681 const S = struct {
1682 const SelfType = @This();
1683 a: ?*SelfType,
1684 tu: TU,
1685 e: E,
1686 vec: Vec2,
1687 };
1688
1689 var inst = S{
1690 .a = null,
1691 .tu = TU{ .ptr = null },
1692 .e = E.Two,
1693 .vec = Vec2{ .x = 10.2, .y = 2.22 },
1694 };
1695 inst.a = &inst;
1696 inst.tu.ptr = &inst.tu;
1697
1698 var buf0 = std.ArrayList(u8).init(std.testing.allocator);
1699 defer buf0.deinit();
1700 try formatType(inst, "", FormatOptions{}, &buf0, @TypeOf(std.ArrayList(u8).appendSlice).ReturnType.ErrorSet, std.ArrayList(u8).appendSlice, 0);
1701 std.testing.expect(mem.eql(u8, buf0.toSlice(), "S{ ... }"));
1702
1703 var buf1 = std.ArrayList(u8).init(std.testing.allocator);
1704 defer buf1.deinit();
1705 try formatType(inst, "", FormatOptions{}, &buf1, @TypeOf(std.ArrayList(u8).appendSlice).ReturnType.ErrorSet, std.ArrayList(u8).appendSlice, 1);
1706 std.testing.expect(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }"));
1707
1708 var buf2 = std.ArrayList(u8).init(std.testing.allocator);
1709 defer buf2.deinit();
1710 try formatType(inst, "", FormatOptions{}, &buf2, @TypeOf(std.ArrayList(u8).appendSlice).ReturnType.ErrorSet, std.ArrayList(u8).appendSlice, 2);
1711 std.testing.expect(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) }"));
1712
1713 var buf3 = std.ArrayList(u8).init(std.testing.allocator);
1714 defer buf3.deinit();
1715 try formatType(inst, "", FormatOptions{}, &buf3, @TypeOf(std.ArrayList(u8).appendSlice).ReturnType.ErrorSet, std.ArrayList(u8).appendSlice, 3);
1716 std.testing.expect(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) }"));
1717}
1718
1719test "positional" {
1720 try testFmt("2 1 0", "{2} {1} {0}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) });
1721 try testFmt("2 1 0", "{2} {1} {}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) });
1722 try testFmt("0 0", "{0} {0}", .{@as(usize, 0)});
1723 try testFmt("0 1", "{} {1}", .{ @as(usize, 0), @as(usize, 1) });
1724 try testFmt("1 0 0 1", "{1} {} {0} {}", .{ @as(usize, 0), @as(usize, 1) });
1725}
1726
1727test "positional with specifier" {
1728 try testFmt("10.0", "{0d:.1}", .{@as(f64, 9.999)});
1729}
1730
1731test "positional/alignment/width/precision" {
1732 try testFmt("10.0", "{0d: >3.1}", .{@as(f64, 9.999)});
1733}
1734
1735test "vector" {
1736 // https://github.com/ziglang/zig/issues/3317
1737 if (builtin.arch == .mipsel) return error.SkipZigTest;
1738
1739 const vbool: @Vector(4, bool) = [_]bool{ true, false, true, false };
1740 const vi64: @Vector(4, i64) = [_]i64{ -2, -1, 0, 1 };
1741 const vu64: @Vector(4, u64) = [_]u64{ 1000, 2000, 3000, 4000 };
1742
1743 try testFmt("{ true, false, true, false }", "{}", .{vbool});
1744 try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64});
1745 try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64});
1746 try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64});
1747 try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64});
1748 try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64});
1749 try testFmt("{ 1000B, 1.953125KiB, 2.9296875KiB, 3.90625KiB }", "{Bi}", .{vu64});
1750}
1751
1752test "enum-literal" {
1753 try testFmt(".hello_world", "{}", .{.hello_world});
1754}
lib/std/std.zig+1
......@@ -38,6 +38,7 @@ pub const elf = @import("elf.zig");
3838pub const event = @import("event.zig");
3939pub const fifo = @import("fifo.zig");
4040pub const fmt = @import("fmt.zig");
41pub const fmtstream = @import("fmtstream.zig");
4142pub const fs = @import("fs.zig");
4243pub const hash = @import("hash.zig");
4344pub const hash_map = @import("hash_map.zig");