blob b45194a8 (109234B) - Raw
1 //! String formatting and parsing. 2 3 const std = @import("std.zig"); 4 const builtin = @import("builtin"); 5 6 const io = std.io; 7 const math = std.math; 8 const assert = std.debug.assert; 9 const mem = std.mem; 10 const unicode = std.unicode; 11 const meta = std.meta; 12 const lossyCast = math.lossyCast; 13 const expectFmt = std.testing.expectFmt; 14 15 pub const default_max_depth = 3; 16 17 pub const Alignment = enum { 18 left, 19 center, 20 right, 21 }; 22 23 const default_alignment = .right; 24 const default_fill_char = ' '; 25 26 pub const FormatOptions = struct { 27 precision: ?usize = null, 28 width: ?usize = null, 29 alignment: Alignment = default_alignment, 30 fill: u21 = default_fill_char, 31 }; 32 33 /// Renders fmt string with args, calling `writer` with slices of bytes. 34 /// If `writer` returns an error, the error is returned from `format` and 35 /// `writer` is not called again. 36 /// 37 /// The format string must be comptime-known and may contain placeholders following 38 /// this format: 39 /// `{[argument][specifier]:[fill][alignment][width].[precision]}` 40 /// 41 /// Above, each word including its surrounding [ and ] is a parameter which you have to replace with something: 42 /// 43 /// - *argument* is either the numeric index or the field name of the argument that should be inserted 44 /// - when using a field name, you are required to enclose the field name (an identifier) in square 45 /// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...} 46 /// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below) 47 /// - *fill* is a single unicode codepoint which is used to pad the formatted text 48 /// - *alignment* is one of the three bytes '<', '^', or '>' to make the text left-, center-, or right-aligned, respectively 49 /// - *width* is the total width of the field in unicode codepoints 50 /// - *precision* specifies how many decimals a formatted number should have 51 /// 52 /// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when 53 /// all parameters after the separator are omitted. 54 /// Only exception is the *fill* parameter. If a non-zero *fill* character is required at the same time as *width* is specified, 55 /// one has to specify *alignment* as well, as otherwise the digit following `:` is interpreted as *width*, not *fill*. 56 /// 57 /// The *specifier* has several options for types: 58 /// - `x` and `X`: output numeric value in hexadecimal notation 59 /// - `s`: 60 /// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination 61 /// - for slices of u8, print the entire slice as a string without zero-termination 62 /// - `e`: output floating point value in scientific notation 63 /// - `d`: output numeric value in decimal notation 64 /// - `b`: output integer value in binary notation 65 /// - `o`: output integer value in octal notation 66 /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. 67 /// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max. 68 /// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value. 69 /// - `!`: output error union value as either the unwrapped value, or the formatted error value; may be followed by a format specifier for the underlying value. 70 /// - `*`: output the address of the value instead of the value itself. 71 /// - `any`: output a value of any type using its default format. 72 /// 73 /// If a formatted user type contains a function of the type 74 /// ``` 75 /// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void 76 /// ``` 77 /// with `?` being the type formatted, this function will be called instead of the default implementation. 78 /// This allows user types to be formatted in a logical manner instead of dumping all fields of the type. 79 /// 80 /// A user type may be a `struct`, `vector`, `union` or `enum` type. 81 /// 82 /// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`. 83 pub fn format( 84 writer: anytype, 85 comptime fmt: []const u8, 86 args: anytype, 87 ) !void { 88 const ArgsType = @TypeOf(args); 89 const args_type_info = @typeInfo(ArgsType); 90 if (args_type_info != .@"struct") { 91 @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType)); 92 } 93 94 const fields_info = args_type_info.@"struct".fields; 95 if (fields_info.len > max_format_args) { 96 @compileError("32 arguments max are supported per format call"); 97 } 98 99 @setEvalBranchQuota(2000000); 100 comptime var arg_state: ArgState = .{ .args_len = fields_info.len }; 101 comptime var i = 0; 102 inline while (i < fmt.len) { 103 const start_index = i; 104 105 inline while (i < fmt.len) : (i += 1) { 106 switch (fmt[i]) { 107 '{', '}' => break, 108 else => {}, 109 } 110 } 111 112 comptime var end_index = i; 113 comptime var unescape_brace = false; 114 115 // Handle {{ and }}, those are un-escaped as single braces 116 if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) { 117 unescape_brace = true; 118 // Make the first brace part of the literal... 119 end_index += 1; 120 // ...and skip both 121 i += 2; 122 } 123 124 // Write out the literal 125 if (start_index != end_index) { 126 try writer.writeAll(fmt[start_index..end_index]); 127 } 128 129 // We've already skipped the other brace, restart the loop 130 if (unescape_brace) continue; 131 132 if (i >= fmt.len) break; 133 134 if (fmt[i] == '}') { 135 @compileError("missing opening {"); 136 } 137 138 // Get past the { 139 comptime assert(fmt[i] == '{'); 140 i += 1; 141 142 const fmt_begin = i; 143 // Find the closing brace 144 inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {} 145 const fmt_end = i; 146 147 if (i >= fmt.len) { 148 @compileError("missing closing }"); 149 } 150 151 // Get past the } 152 comptime assert(fmt[i] == '}'); 153 i += 1; 154 155 const placeholder = comptime Placeholder.parse(fmt[fmt_begin..fmt_end].*); 156 const arg_pos = comptime switch (placeholder.arg) { 157 .none => null, 158 .number => |pos| pos, 159 .named => |arg_name| meta.fieldIndex(ArgsType, arg_name) orelse 160 @compileError("no argument with name '" ++ arg_name ++ "'"), 161 }; 162 163 const width = switch (placeholder.width) { 164 .none => null, 165 .number => |v| v, 166 .named => |arg_name| blk: { 167 const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse 168 @compileError("no argument with name '" ++ arg_name ++ "'"); 169 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); 170 break :blk @field(args, arg_name); 171 }, 172 }; 173 174 const precision = switch (placeholder.precision) { 175 .none => null, 176 .number => |v| v, 177 .named => |arg_name| blk: { 178 const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse 179 @compileError("no argument with name '" ++ arg_name ++ "'"); 180 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); 181 break :blk @field(args, arg_name); 182 }, 183 }; 184 185 const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse 186 @compileError("too few arguments"); 187 188 try formatType( 189 @field(args, fields_info[arg_to_print].name), 190 placeholder.specifier_arg, 191 FormatOptions{ 192 .fill = placeholder.fill, 193 .alignment = placeholder.alignment, 194 .width = width, 195 .precision = precision, 196 }, 197 writer, 198 std.options.fmt_max_depth, 199 ); 200 } 201 202 if (comptime arg_state.hasUnusedArgs()) { 203 const missing_count = arg_state.args_len - @popCount(arg_state.used_args); 204 switch (missing_count) { 205 0 => unreachable, 206 1 => @compileError("unused argument in '" ++ fmt ++ "'"), 207 else => @compileError(comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"), 208 } 209 } 210 } 211 212 fn cacheString(str: anytype) []const u8 { 213 return &str; 214 } 215 216 pub const Placeholder = struct { 217 specifier_arg: []const u8, 218 fill: u21, 219 alignment: Alignment, 220 arg: Specifier, 221 width: Specifier, 222 precision: Specifier, 223 224 pub fn parse(comptime str: anytype) Placeholder { 225 const view = std.unicode.Utf8View.initComptime(&str); 226 comptime var parser = Parser{ 227 .iter = view.iterator(), 228 }; 229 230 // Parse the positional argument number 231 const arg = comptime parser.specifier() catch |err| 232 @compileError(@errorName(err)); 233 234 // Parse the format specifier 235 const specifier_arg = comptime parser.until(':'); 236 237 // Skip the colon, if present 238 if (comptime parser.char()) |ch| { 239 if (ch != ':') { 240 @compileError("expected : or }, found '" ++ unicode.utf8EncodeComptime(ch) ++ "'"); 241 } 242 } 243 244 // Parse the fill character, if present. 245 // When the width field is also specified, the fill character must 246 // be followed by an alignment specifier, unless it's '0' (zero) 247 // (in which case it's handled as part of the width specifier) 248 var fill: ?u21 = comptime if (parser.peek(1)) |ch| 249 switch (ch) { 250 '<', '^', '>' => parser.char(), 251 else => null, 252 } 253 else 254 null; 255 256 // Parse the alignment parameter 257 const alignment: ?Alignment = comptime if (parser.peek(0)) |ch| init: { 258 switch (ch) { 259 '<', '^', '>' => { 260 // consume the character 261 break :init switch (parser.char().?) { 262 '<' => .left, 263 '^' => .center, 264 else => .right, 265 }; 266 }, 267 else => break :init null, 268 } 269 } else null; 270 271 // When none of the fill character and the alignment specifier have 272 // been provided, check whether the width starts with a zero. 273 if (fill == null and alignment == null) { 274 fill = comptime if (parser.peek(0) == '0') '0' else null; 275 } 276 277 // Parse the width parameter 278 const width = comptime parser.specifier() catch |err| 279 @compileError(@errorName(err)); 280 281 // Skip the dot, if present 282 if (comptime parser.char()) |ch| { 283 if (ch != '.') { 284 @compileError("expected . or }, found '" ++ unicode.utf8EncodeComptime(ch) ++ "'"); 285 } 286 } 287 288 // Parse the precision parameter 289 const precision = comptime parser.specifier() catch |err| 290 @compileError(@errorName(err)); 291 292 if (comptime parser.char()) |ch| { 293 @compileError("extraneous trailing character '" ++ unicode.utf8EncodeComptime(ch) ++ "'"); 294 } 295 296 return Placeholder{ 297 .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*), 298 .fill = fill orelse default_fill_char, 299 .alignment = alignment orelse default_alignment, 300 .arg = arg, 301 .width = width, 302 .precision = precision, 303 }; 304 } 305 }; 306 307 pub const Specifier = union(enum) { 308 none, 309 number: usize, 310 named: []const u8, 311 }; 312 313 /// A stream based parser for format strings. 314 /// 315 /// Allows to implement formatters compatible with std.fmt without replicating 316 /// the standard library behavior. 317 pub const Parser = struct { 318 pos: usize = 0, 319 iter: std.unicode.Utf8Iterator, 320 321 // Returns a decimal number or null if the current character is not a 322 // digit 323 pub fn number(self: *@This()) ?usize { 324 var r: ?usize = null; 325 326 while (self.peek(0)) |code_point| { 327 switch (code_point) { 328 '0'...'9' => { 329 if (r == null) r = 0; 330 r.? *= 10; 331 r.? += code_point - '0'; 332 }, 333 else => break, 334 } 335 _ = self.iter.nextCodepoint(); 336 } 337 338 return r; 339 } 340 341 // Returns a substring of the input starting from the current position 342 // and ending where `ch` is found or until the end if not found 343 pub fn until(self: *@This(), ch: u21) []const u8 { 344 var result: []const u8 = &[_]u8{}; 345 while (self.peek(0)) |code_point| { 346 if (code_point == ch) 347 break; 348 result = result ++ (self.iter.nextCodepointSlice() orelse &[_]u8{}); 349 } 350 return result; 351 } 352 353 // Returns one character, if available 354 pub fn char(self: *@This()) ?u21 { 355 if (self.iter.nextCodepoint()) |code_point| { 356 return code_point; 357 } 358 return null; 359 } 360 361 pub fn maybe(self: *@This(), val: u21) bool { 362 if (self.peek(0) == val) { 363 _ = self.iter.nextCodepoint(); 364 return true; 365 } 366 return false; 367 } 368 369 // Returns a decimal number or null if the current character is not a 370 // digit 371 pub fn specifier(self: *@This()) !Specifier { 372 if (self.maybe('[')) { 373 const arg_name = self.until(']'); 374 375 if (!self.maybe(']')) 376 return @field(anyerror, "Expected closing ]"); 377 378 return Specifier{ .named = arg_name }; 379 } 380 if (self.number()) |i| 381 return Specifier{ .number = i }; 382 383 return Specifier{ .none = {} }; 384 } 385 386 // Returns the n-th next character or null if that's past the end 387 pub fn peek(self: *@This(), n: usize) ?u21 { 388 const original_i = self.iter.i; 389 defer self.iter.i = original_i; 390 391 var i: usize = 0; 392 var code_point: ?u21 = null; 393 while (i <= n) : (i += 1) { 394 code_point = self.iter.nextCodepoint(); 395 if (code_point == null) return null; 396 } 397 return code_point; 398 } 399 }; 400 401 pub const ArgSetType = u32; 402 const max_format_args = @typeInfo(ArgSetType).int.bits; 403 404 pub const ArgState = struct { 405 next_arg: usize = 0, 406 used_args: ArgSetType = 0, 407 args_len: usize, 408 409 pub fn hasUnusedArgs(self: *@This()) bool { 410 return @popCount(self.used_args) != self.args_len; 411 } 412 413 pub fn nextArg(self: *@This(), arg_index: ?usize) ?usize { 414 const next_index = arg_index orelse init: { 415 const arg = self.next_arg; 416 self.next_arg += 1; 417 break :init arg; 418 }; 419 420 if (next_index >= self.args_len) { 421 return null; 422 } 423 424 // Mark this argument as used 425 self.used_args |= @as(ArgSetType, 1) << @as(u5, @intCast(next_index)); 426 return next_index; 427 } 428 }; 429 430 pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 431 _ = options; 432 const T = @TypeOf(value); 433 434 switch (@typeInfo(T)) { 435 .pointer => |info| { 436 try writer.writeAll(@typeName(info.child) ++ "@"); 437 if (info.size == .Slice) 438 try formatInt(@intFromPtr(value.ptr), 16, .lower, FormatOptions{}, writer) 439 else 440 try formatInt(@intFromPtr(value), 16, .lower, FormatOptions{}, writer); 441 return; 442 }, 443 .optional => |info| { 444 if (@typeInfo(info.child) == .pointer) { 445 try writer.writeAll(@typeName(info.child) ++ "@"); 446 try formatInt(@intFromPtr(value), 16, .lower, FormatOptions{}, writer); 447 return; 448 } 449 }, 450 else => {}, 451 } 452 453 @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); 454 } 455 456 // This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 457 const ANY = "any"; 458 459 pub fn defaultSpec(comptime T: type) [:0]const u8 { 460 switch (@typeInfo(T)) { 461 .array, .vector => return ANY, 462 .pointer => |ptr_info| switch (ptr_info.size) { 463 .One => switch (@typeInfo(ptr_info.child)) { 464 .array => return ANY, 465 else => {}, 466 }, 467 .Many, .C => return "*", 468 .Slice => return ANY, 469 }, 470 .optional => |info| return "?" ++ defaultSpec(info.child), 471 .error_union => |info| return "!" ++ defaultSpec(info.payload), 472 else => {}, 473 } 474 return ""; 475 } 476 477 fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 { 478 return if (std.mem.eql(u8, fmt[1..], ANY)) 479 ANY 480 else 481 fmt[1..]; 482 } 483 484 pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) void { 485 @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); 486 } 487 488 pub fn formatType( 489 value: anytype, 490 comptime fmt: []const u8, 491 options: FormatOptions, 492 writer: anytype, 493 max_depth: usize, 494 ) @TypeOf(writer).Error!void { 495 const T = @TypeOf(value); 496 const actual_fmt = comptime if (std.mem.eql(u8, fmt, ANY)) 497 defaultSpec(T) 498 else if (fmt.len != 0 and (fmt[0] == '?' or fmt[0] == '!')) switch (@typeInfo(T)) { 499 .optional, .error_union => fmt, 500 else => stripOptionalOrErrorUnionSpec(fmt), 501 } else fmt; 502 503 if (comptime std.mem.eql(u8, actual_fmt, "*")) { 504 return formatAddress(value, options, writer); 505 } 506 507 if (std.meta.hasMethod(T, "format")) { 508 return try value.format(actual_fmt, options, writer); 509 } 510 511 switch (@typeInfo(T)) { 512 .comptime_int, .int, .comptime_float, .float => { 513 return formatValue(value, actual_fmt, options, writer); 514 }, 515 .void => { 516 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 517 return formatBuf("void", options, writer); 518 }, 519 .bool => { 520 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 521 return formatBuf(if (value) "true" else "false", options, writer); 522 }, 523 .optional => { 524 if (actual_fmt.len == 0 or actual_fmt[0] != '?') 525 @compileError("cannot format optional without a specifier (i.e. {?} or {any})"); 526 const remaining_fmt = comptime stripOptionalOrErrorUnionSpec(actual_fmt); 527 if (value) |payload| { 528 return formatType(payload, remaining_fmt, options, writer, max_depth); 529 } else { 530 return formatBuf("null", options, writer); 531 } 532 }, 533 .error_union => { 534 if (actual_fmt.len == 0 or actual_fmt[0] != '!') 535 @compileError("cannot format error union without a specifier (i.e. {!} or {any})"); 536 const remaining_fmt = comptime stripOptionalOrErrorUnionSpec(actual_fmt); 537 if (value) |payload| { 538 return formatType(payload, remaining_fmt, options, writer, max_depth); 539 } else |err| { 540 return formatType(err, "", options, writer, max_depth); 541 } 542 }, 543 .error_set => { 544 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 545 try writer.writeAll("error."); 546 return writer.writeAll(@errorName(value)); 547 }, 548 .@"enum" => |enumInfo| { 549 try writer.writeAll(@typeName(T)); 550 if (enumInfo.is_exhaustive) { 551 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 552 try writer.writeAll("."); 553 try writer.writeAll(@tagName(value)); 554 return; 555 } 556 557 // Use @tagName only if value is one of known fields 558 @setEvalBranchQuota(3 * enumInfo.fields.len); 559 inline for (enumInfo.fields) |enumField| { 560 if (@intFromEnum(value) == enumField.value) { 561 try writer.writeAll("."); 562 try writer.writeAll(@tagName(value)); 563 return; 564 } 565 } 566 567 try writer.writeAll("("); 568 try formatType(@intFromEnum(value), actual_fmt, options, writer, max_depth); 569 try writer.writeAll(")"); 570 }, 571 .@"union" => |info| { 572 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 573 try writer.writeAll(@typeName(T)); 574 if (max_depth == 0) { 575 return writer.writeAll("{ ... }"); 576 } 577 if (info.tag_type) |UnionTagType| { 578 try writer.writeAll("{ ."); 579 try writer.writeAll(@tagName(@as(UnionTagType, value))); 580 try writer.writeAll(" = "); 581 inline for (info.fields) |u_field| { 582 if (value == @field(UnionTagType, u_field.name)) { 583 try formatType(@field(value, u_field.name), ANY, options, writer, max_depth - 1); 584 } 585 } 586 try writer.writeAll(" }"); 587 } else { 588 try format(writer, "@{x}", .{@intFromPtr(&value)}); 589 } 590 }, 591 .@"struct" => |info| { 592 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 593 if (info.is_tuple) { 594 // Skip the type and field names when formatting tuples. 595 if (max_depth == 0) { 596 return writer.writeAll("{ ... }"); 597 } 598 try writer.writeAll("{"); 599 inline for (info.fields, 0..) |f, i| { 600 if (i == 0) { 601 try writer.writeAll(" "); 602 } else { 603 try writer.writeAll(", "); 604 } 605 try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1); 606 } 607 return writer.writeAll(" }"); 608 } 609 try writer.writeAll(@typeName(T)); 610 if (max_depth == 0) { 611 return writer.writeAll("{ ... }"); 612 } 613 try writer.writeAll("{"); 614 inline for (info.fields, 0..) |f, i| { 615 if (i == 0) { 616 try writer.writeAll(" ."); 617 } else { 618 try writer.writeAll(", ."); 619 } 620 try writer.writeAll(f.name); 621 try writer.writeAll(" = "); 622 try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1); 623 } 624 try writer.writeAll(" }"); 625 }, 626 .pointer => |ptr_info| switch (ptr_info.size) { 627 .One => switch (@typeInfo(ptr_info.child)) { 628 .array, .@"enum", .@"union", .@"struct" => { 629 return formatType(value.*, actual_fmt, options, writer, max_depth); 630 }, 631 else => return format(writer, "{s}@{x}", .{ @typeName(ptr_info.child), @intFromPtr(value) }), 632 }, 633 .Many, .C => { 634 if (actual_fmt.len == 0) 635 @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); 636 if (ptr_info.sentinel) |_| { 637 return formatType(mem.span(value), actual_fmt, options, writer, max_depth); 638 } 639 if (actual_fmt[0] == 's' and ptr_info.child == u8) { 640 return formatBuf(mem.span(value), options, writer); 641 } 642 invalidFmtError(fmt, value); 643 }, 644 .Slice => { 645 if (actual_fmt.len == 0) 646 @compileError("cannot format slice without a specifier (i.e. {s} or {any})"); 647 if (max_depth == 0) { 648 return writer.writeAll("{ ... }"); 649 } 650 if (actual_fmt[0] == 's' and ptr_info.child == u8) { 651 return formatBuf(value, options, writer); 652 } 653 try writer.writeAll("{ "); 654 for (value, 0..) |elem, i| { 655 try formatType(elem, actual_fmt, options, writer, max_depth - 1); 656 if (i != value.len - 1) { 657 try writer.writeAll(", "); 658 } 659 } 660 try writer.writeAll(" }"); 661 }, 662 }, 663 .array => |info| { 664 if (actual_fmt.len == 0) 665 @compileError("cannot format array without a specifier (i.e. {s} or {any})"); 666 if (max_depth == 0) { 667 return writer.writeAll("{ ... }"); 668 } 669 if (actual_fmt[0] == 's' and info.child == u8) { 670 return formatBuf(&value, options, writer); 671 } 672 try writer.writeAll("{ "); 673 for (value, 0..) |elem, i| { 674 try formatType(elem, actual_fmt, options, writer, max_depth - 1); 675 if (i < value.len - 1) { 676 try writer.writeAll(", "); 677 } 678 } 679 try writer.writeAll(" }"); 680 }, 681 .vector => |info| { 682 try writer.writeAll("{ "); 683 var i: usize = 0; 684 while (i < info.len) : (i += 1) { 685 try formatType(value[i], actual_fmt, options, writer, max_depth - 1); 686 if (i < info.len - 1) { 687 try writer.writeAll(", "); 688 } 689 } 690 try writer.writeAll(" }"); 691 }, 692 .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"), 693 .type => { 694 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 695 return formatBuf(@typeName(value), options, writer); 696 }, 697 .enum_literal => { 698 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 699 const buffer = [_]u8{'.'} ++ @tagName(value); 700 return formatBuf(buffer, options, writer); 701 }, 702 .null => { 703 if (actual_fmt.len != 0) invalidFmtError(fmt, value); 704 return formatBuf("null", options, writer); 705 }, 706 else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"), 707 } 708 } 709 710 fn formatValue( 711 value: anytype, 712 comptime fmt: []const u8, 713 options: FormatOptions, 714 writer: anytype, 715 ) !void { 716 const T = @TypeOf(value); 717 switch (@typeInfo(T)) { 718 .float, .comptime_float => return formatFloatValue(value, fmt, options, writer), 719 .int, .comptime_int => return formatIntValue(value, fmt, options, writer), 720 .bool => return formatBuf(if (value) "true" else "false", options, writer), 721 else => comptime unreachable, 722 } 723 } 724 725 pub fn formatIntValue( 726 value: anytype, 727 comptime fmt: []const u8, 728 options: FormatOptions, 729 writer: anytype, 730 ) !void { 731 comptime var base = 10; 732 comptime var case: Case = .lower; 733 734 const int_value = if (@TypeOf(value) == comptime_int) blk: { 735 const Int = math.IntFittingRange(value, value); 736 break :blk @as(Int, value); 737 } else value; 738 739 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { 740 base = 10; 741 case = .lower; 742 } else if (comptime std.mem.eql(u8, fmt, "c")) { 743 if (@typeInfo(@TypeOf(int_value)).int.bits <= 8) { 744 return formatAsciiChar(@as(u8, int_value), options, writer); 745 } else { 746 @compileError("cannot print integer that is larger than 8 bits as an ASCII character"); 747 } 748 } else if (comptime std.mem.eql(u8, fmt, "u")) { 749 if (@typeInfo(@TypeOf(int_value)).int.bits <= 21) { 750 return formatUnicodeCodepoint(@as(u21, int_value), options, writer); 751 } else { 752 @compileError("cannot print integer that is larger than 21 bits as an UTF-8 sequence"); 753 } 754 } else if (comptime std.mem.eql(u8, fmt, "b")) { 755 base = 2; 756 case = .lower; 757 } else if (comptime std.mem.eql(u8, fmt, "x")) { 758 base = 16; 759 case = .lower; 760 } else if (comptime std.mem.eql(u8, fmt, "X")) { 761 base = 16; 762 case = .upper; 763 } else if (comptime std.mem.eql(u8, fmt, "o")) { 764 base = 8; 765 case = .lower; 766 } else { 767 invalidFmtError(fmt, value); 768 } 769 770 return formatInt(int_value, base, case, options, writer); 771 } 772 773 pub const format_float = @import("fmt/format_float.zig"); 774 pub const formatFloat = format_float.formatFloat; 775 pub const FormatFloatError = format_float.FormatError; 776 777 fn formatFloatValue( 778 value: anytype, 779 comptime fmt: []const u8, 780 options: FormatOptions, 781 writer: anytype, 782 ) !void { 783 var buf: [format_float.bufferSize(.decimal, f64)]u8 = undefined; 784 785 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { 786 const s = formatFloat(&buf, value, .{ .mode = .scientific, .precision = options.precision }) catch |err| switch (err) { 787 error.BufferTooSmall => "(float)", 788 }; 789 return formatBuf(s, options, writer); 790 } else if (comptime std.mem.eql(u8, fmt, "d")) { 791 const s = formatFloat(&buf, value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { 792 error.BufferTooSmall => "(float)", 793 }; 794 return formatBuf(s, options, writer); 795 } else if (comptime std.mem.eql(u8, fmt, "x")) { 796 var buf_stream = std.io.fixedBufferStream(&buf); 797 formatFloatHexadecimal(value, options, buf_stream.writer()) catch |err| switch (err) { 798 error.NoSpaceLeft => unreachable, 799 }; 800 return formatBuf(buf_stream.getWritten(), options, writer); 801 } else { 802 invalidFmtError(fmt, value); 803 } 804 } 805 806 test { 807 _ = &format_float; 808 } 809 810 pub const Case = enum { lower, upper }; 811 812 fn SliceHex(comptime case: Case) type { 813 const charset = "0123456789" ++ if (case == .upper) "ABCDEF" else "abcdef"; 814 815 return struct { 816 pub fn format( 817 bytes: []const u8, 818 comptime fmt: []const u8, 819 options: std.fmt.FormatOptions, 820 writer: anytype, 821 ) !void { 822 _ = fmt; 823 _ = options; 824 var buf: [2]u8 = undefined; 825 826 for (bytes) |c| { 827 buf[0] = charset[c >> 4]; 828 buf[1] = charset[c & 15]; 829 try writer.writeAll(&buf); 830 } 831 } 832 }; 833 } 834 835 const formatSliceHexLower = SliceHex(.lower).format; 836 const formatSliceHexUpper = SliceHex(.upper).format; 837 838 /// Return a Formatter for a []const u8 where every byte is formatted as a pair 839 /// of lowercase hexadecimal digits. 840 pub fn fmtSliceHexLower(bytes: []const u8) std.fmt.Formatter(formatSliceHexLower) { 841 return .{ .data = bytes }; 842 } 843 844 /// Return a Formatter for a []const u8 where every byte is formatted as pair 845 /// of uppercase hexadecimal digits. 846 pub fn fmtSliceHexUpper(bytes: []const u8) std.fmt.Formatter(formatSliceHexUpper) { 847 return .{ .data = bytes }; 848 } 849 850 fn SliceEscape(comptime case: Case) type { 851 const charset = "0123456789" ++ if (case == .upper) "ABCDEF" else "abcdef"; 852 853 return struct { 854 pub fn format( 855 bytes: []const u8, 856 comptime fmt: []const u8, 857 options: std.fmt.FormatOptions, 858 writer: anytype, 859 ) !void { 860 _ = fmt; 861 _ = options; 862 var buf: [4]u8 = undefined; 863 864 buf[0] = '\\'; 865 buf[1] = 'x'; 866 867 for (bytes) |c| { 868 if (std.ascii.isPrint(c)) { 869 try writer.writeByte(c); 870 } else { 871 buf[2] = charset[c >> 4]; 872 buf[3] = charset[c & 15]; 873 try writer.writeAll(&buf); 874 } 875 } 876 } 877 }; 878 } 879 880 const formatSliceEscapeLower = SliceEscape(.lower).format; 881 const formatSliceEscapeUpper = SliceEscape(.upper).format; 882 883 /// Return a Formatter for a []const u8 where every non-printable ASCII 884 /// character is escaped as \xNN, where NN is the character in lowercase 885 /// hexadecimal notation. 886 pub fn fmtSliceEscapeLower(bytes: []const u8) std.fmt.Formatter(formatSliceEscapeLower) { 887 return .{ .data = bytes }; 888 } 889 890 /// Return a Formatter for a []const u8 where every non-printable ASCII 891 /// character is escaped as \xNN, where NN is the character in uppercase 892 /// hexadecimal notation. 893 pub fn fmtSliceEscapeUpper(bytes: []const u8) std.fmt.Formatter(formatSliceEscapeUpper) { 894 return .{ .data = bytes }; 895 } 896 897 fn Size(comptime base: comptime_int) type { 898 return struct { 899 fn format( 900 value: u64, 901 comptime fmt: []const u8, 902 options: FormatOptions, 903 writer: anytype, 904 ) !void { 905 _ = fmt; 906 if (value == 0) { 907 return formatBuf("0B", options, writer); 908 } 909 // The worst case in terms of space needed is 32 bytes + 3 for the suffix. 910 var buf: [format_float.min_buffer_size + 3]u8 = undefined; 911 912 const mags_si = " kMGTPEZY"; 913 const mags_iec = " KMGTPEZY"; 914 915 const log2 = math.log2(value); 916 const magnitude = switch (base) { 917 1000 => @min(log2 / comptime math.log2(1000), mags_si.len - 1), 918 1024 => @min(log2 / 10, mags_iec.len - 1), 919 else => unreachable, 920 }; 921 const new_value = lossyCast(f64, value) / math.pow(f64, lossyCast(f64, base), lossyCast(f64, magnitude)); 922 const suffix = switch (base) { 923 1000 => mags_si[magnitude], 924 1024 => mags_iec[magnitude], 925 else => unreachable, 926 }; 927 928 const s = switch (magnitude) { 929 0 => buf[0..formatIntBuf(&buf, value, 10, .lower, .{})], 930 else => formatFloat(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { 931 error.BufferTooSmall => unreachable, 932 }, 933 }; 934 935 var i: usize = s.len; 936 if (suffix == ' ') { 937 buf[i] = 'B'; 938 i += 1; 939 } else switch (base) { 940 1000 => { 941 buf[i..][0..2].* = [_]u8{ suffix, 'B' }; 942 i += 2; 943 }, 944 1024 => { 945 buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' }; 946 i += 3; 947 }, 948 else => unreachable, 949 } 950 951 return formatBuf(buf[0..i], options, writer); 952 } 953 }; 954 } 955 const formatSizeDec = Size(1000).format; 956 const formatSizeBin = Size(1024).format; 957 958 /// Return a Formatter for a u64 value representing a file size. 959 /// This formatter represents the number as multiple of 1000 and uses the SI 960 /// measurement units (kB, MB, GB, ...). 961 /// Format option `precision` is ignored when `value` is less than 1kB 962 pub fn fmtIntSizeDec(value: u64) std.fmt.Formatter(formatSizeDec) { 963 return .{ .data = value }; 964 } 965 966 /// Return a Formatter for a u64 value representing a file size. 967 /// This formatter represents the number as multiple of 1024 and uses the IEC 968 /// measurement units (KiB, MiB, GiB, ...). 969 /// Format option `precision` is ignored when `value` is less than 1KiB 970 pub fn fmtIntSizeBin(value: u64) std.fmt.Formatter(formatSizeBin) { 971 return .{ .data = value }; 972 } 973 974 fn checkTextFmt(comptime fmt: []const u8) void { 975 if (fmt.len != 1) 976 @compileError("unsupported format string '" ++ fmt ++ "' when formatting text"); 977 switch (fmt[0]) { 978 // Example of deprecation: 979 // '[deprecated_specifier]' => @compileError("specifier '[deprecated_specifier]' has been deprecated, wrap your argument in `std.some_function` instead"), 980 'x' => @compileError("specifier 'x' has been deprecated, wrap your argument in std.fmt.fmtSliceHexLower instead"), 981 'X' => @compileError("specifier 'X' has been deprecated, wrap your argument in std.fmt.fmtSliceHexUpper instead"), 982 else => {}, 983 } 984 } 985 986 pub fn formatText( 987 bytes: []const u8, 988 comptime fmt: []const u8, 989 options: FormatOptions, 990 writer: anytype, 991 ) !void { 992 comptime checkTextFmt(fmt); 993 return formatBuf(bytes, options, writer); 994 } 995 996 pub fn formatAsciiChar( 997 c: u8, 998 options: FormatOptions, 999 writer: anytype, 1000 ) !void { 1001 return formatBuf(@as(*const [1]u8, &c), options, writer); 1002 } 1003 1004 pub fn formatUnicodeCodepoint( 1005 c: u21, 1006 options: FormatOptions, 1007 writer: anytype, 1008 ) !void { 1009 var buf: [4]u8 = undefined; 1010 const len = unicode.utf8Encode(c, &buf) catch |err| switch (err) { 1011 error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => { 1012 return formatBuf(&unicode.utf8EncodeComptime(unicode.replacement_character), options, writer); 1013 }, 1014 }; 1015 return formatBuf(buf[0..len], options, writer); 1016 } 1017 1018 pub fn formatBuf( 1019 buf: []const u8, 1020 options: FormatOptions, 1021 writer: anytype, 1022 ) !void { 1023 if (options.width) |min_width| { 1024 // In case of error assume the buffer content is ASCII-encoded 1025 const width = unicode.utf8CountCodepoints(buf) catch buf.len; 1026 const padding = if (width < min_width) min_width - width else 0; 1027 1028 if (padding == 0) 1029 return writer.writeAll(buf); 1030 1031 var fill_buffer: [4]u8 = undefined; 1032 const fill_utf8 = if (unicode.utf8Encode(options.fill, &fill_buffer)) |len| 1033 fill_buffer[0..len] 1034 else |err| switch (err) { 1035 error.Utf8CannotEncodeSurrogateHalf, 1036 error.CodepointTooLarge, 1037 => &unicode.utf8EncodeComptime(unicode.replacement_character), 1038 }; 1039 switch (options.alignment) { 1040 .left => { 1041 try writer.writeAll(buf); 1042 try writer.writeBytesNTimes(fill_utf8, padding); 1043 }, 1044 .center => { 1045 const left_padding = padding / 2; 1046 const right_padding = (padding + 1) / 2; 1047 try writer.writeBytesNTimes(fill_utf8, left_padding); 1048 try writer.writeAll(buf); 1049 try writer.writeBytesNTimes(fill_utf8, right_padding); 1050 }, 1051 .right => { 1052 try writer.writeBytesNTimes(fill_utf8, padding); 1053 try writer.writeAll(buf); 1054 }, 1055 } 1056 } else { 1057 // Fast path, avoid counting the number of codepoints 1058 try writer.writeAll(buf); 1059 } 1060 } 1061 1062 pub fn formatFloatHexadecimal( 1063 value: anytype, 1064 options: FormatOptions, 1065 writer: anytype, 1066 ) !void { 1067 if (math.signbit(value)) { 1068 try writer.writeByte('-'); 1069 } 1070 if (math.isNan(value)) { 1071 return writer.writeAll("nan"); 1072 } 1073 if (math.isInf(value)) { 1074 return writer.writeAll("inf"); 1075 } 1076 1077 const T = @TypeOf(value); 1078 const TU = std.meta.Int(.unsigned, @bitSizeOf(T)); 1079 1080 const mantissa_bits = math.floatMantissaBits(T); 1081 const fractional_bits = math.floatFractionalBits(T); 1082 const exponent_bits = math.floatExponentBits(T); 1083 const mantissa_mask = (1 << mantissa_bits) - 1; 1084 const exponent_mask = (1 << exponent_bits) - 1; 1085 const exponent_bias = (1 << (exponent_bits - 1)) - 1; 1086 1087 const as_bits = @as(TU, @bitCast(value)); 1088 var mantissa = as_bits & mantissa_mask; 1089 var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask)); 1090 1091 const is_denormal = exponent == 0 and mantissa != 0; 1092 const is_zero = exponent == 0 and mantissa == 0; 1093 1094 if (is_zero) { 1095 // Handle this case here to simplify the logic below. 1096 try writer.writeAll("0x0"); 1097 if (options.precision) |precision| { 1098 if (precision > 0) { 1099 try writer.writeAll("."); 1100 try writer.writeByteNTimes('0', precision); 1101 } 1102 } else { 1103 try writer.writeAll(".0"); 1104 } 1105 try writer.writeAll("p0"); 1106 return; 1107 } 1108 1109 if (is_denormal) { 1110 // Adjust the exponent for printing. 1111 exponent += 1; 1112 } else { 1113 if (fractional_bits == mantissa_bits) 1114 mantissa |= 1 << fractional_bits; // Add the implicit integer bit. 1115 } 1116 1117 const mantissa_digits = (fractional_bits + 3) / 4; 1118 // Fill in zeroes to round the fraction width to a multiple of 4. 1119 mantissa <<= mantissa_digits * 4 - fractional_bits; 1120 1121 if (options.precision) |precision| { 1122 // Round if needed. 1123 if (precision < mantissa_digits) { 1124 // We always have at least 4 extra bits. 1125 var extra_bits = (mantissa_digits - precision) * 4; 1126 // The result LSB is the Guard bit, we need two more (Round and 1127 // Sticky) to round the value. 1128 while (extra_bits > 2) { 1129 mantissa = (mantissa >> 1) | (mantissa & 1); 1130 extra_bits -= 1; 1131 } 1132 // Round to nearest, tie to even. 1133 mantissa |= @intFromBool(mantissa & 0b100 != 0); 1134 mantissa += 1; 1135 // Drop the excess bits. 1136 mantissa >>= 2; 1137 // Restore the alignment. 1138 mantissa <<= @as(math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4)); 1139 1140 const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; 1141 // Prefer a normalized result in case of overflow. 1142 if (overflow) { 1143 mantissa >>= 1; 1144 exponent += 1; 1145 } 1146 } 1147 } 1148 1149 // +1 for the decimal part. 1150 var buf: [1 + mantissa_digits]u8 = undefined; 1151 _ = formatIntBuf(&buf, mantissa, 16, .lower, .{ .fill = '0', .width = 1 + mantissa_digits }); 1152 1153 try writer.writeAll("0x"); 1154 try writer.writeByte(buf[0]); 1155 const trimmed = mem.trimRight(u8, buf[1..], "0"); 1156 if (options.precision) |precision| { 1157 if (precision > 0) try writer.writeAll("."); 1158 } else if (trimmed.len > 0) { 1159 try writer.writeAll("."); 1160 } 1161 try writer.writeAll(trimmed); 1162 // Add trailing zeros if explicitly requested. 1163 if (options.precision) |precision| if (precision > 0) { 1164 if (precision > trimmed.len) 1165 try writer.writeByteNTimes('0', precision - trimmed.len); 1166 }; 1167 try writer.writeAll("p"); 1168 try formatInt(exponent - exponent_bias, 10, .lower, .{}, writer); 1169 } 1170 1171 pub fn formatInt( 1172 value: anytype, 1173 base: u8, 1174 case: Case, 1175 options: FormatOptions, 1176 writer: anytype, 1177 ) !void { 1178 assert(base >= 2); 1179 1180 const int_value = if (@TypeOf(value) == comptime_int) blk: { 1181 const Int = math.IntFittingRange(value, value); 1182 break :blk @as(Int, value); 1183 } else value; 1184 1185 const value_info = @typeInfo(@TypeOf(int_value)).int; 1186 1187 // The type must have the same size as `base` or be wider in order for the 1188 // division to work 1189 const min_int_bits = comptime @max(value_info.bits, 8); 1190 const MinInt = std.meta.Int(.unsigned, min_int_bits); 1191 1192 const abs_value = @abs(int_value); 1193 // The worst case in terms of space needed is base 2, plus 1 for the sign 1194 var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined; 1195 1196 var a: MinInt = abs_value; 1197 var index: usize = buf.len; 1198 1199 if (base == 10) { 1200 while (a >= 100) : (a = @divTrunc(a, 100)) { 1201 index -= 2; 1202 buf[index..][0..2].* = digits2(@intCast(a % 100)); 1203 } 1204 1205 if (a < 10) { 1206 index -= 1; 1207 buf[index] = '0' + @as(u8, @intCast(a)); 1208 } else { 1209 index -= 2; 1210 buf[index..][0..2].* = digits2(@intCast(a)); 1211 } 1212 } else { 1213 while (true) { 1214 const digit = a % base; 1215 index -= 1; 1216 buf[index] = digitToChar(@intCast(digit), case); 1217 a /= base; 1218 if (a == 0) break; 1219 } 1220 } 1221 1222 if (value_info.signedness == .signed) { 1223 if (value < 0) { 1224 // Negative integer 1225 index -= 1; 1226 buf[index] = '-'; 1227 } else if (options.width == null or options.width.? == 0) { 1228 // Positive integer, omit the plus sign 1229 } else { 1230 // Positive integer 1231 index -= 1; 1232 buf[index] = '+'; 1233 } 1234 } 1235 1236 return formatBuf(buf[index..], options, writer); 1237 } 1238 1239 pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, case: Case, options: FormatOptions) usize { 1240 var fbs = std.io.fixedBufferStream(out_buf); 1241 formatInt(value, base, case, options, fbs.writer()) catch unreachable; 1242 return fbs.pos; 1243 } 1244 1245 // Converts values in the range [0, 100) to a string. 1246 pub fn digits2(value: usize) [2]u8 { 1247 return "00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899"[value * 2 ..][0..2].*; 1248 } 1249 1250 const FormatDurationData = struct { 1251 ns: u64, 1252 negative: bool = false, 1253 }; 1254 1255 fn formatDuration(data: FormatDurationData, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1256 _ = fmt; 1257 1258 // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 1259 var buf: [24]u8 = undefined; 1260 var fbs = std.io.fixedBufferStream(&buf); 1261 var buf_writer = fbs.writer(); 1262 if (data.negative) { 1263 buf_writer.writeByte('-') catch unreachable; 1264 } 1265 1266 var ns_remaining = data.ns; 1267 inline for (.{ 1268 .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, 1269 .{ .ns = std.time.ns_per_week, .sep = 'w' }, 1270 .{ .ns = std.time.ns_per_day, .sep = 'd' }, 1271 .{ .ns = std.time.ns_per_hour, .sep = 'h' }, 1272 .{ .ns = std.time.ns_per_min, .sep = 'm' }, 1273 }) |unit| { 1274 if (ns_remaining >= unit.ns) { 1275 const units = ns_remaining / unit.ns; 1276 formatInt(units, 10, .lower, .{}, buf_writer) catch unreachable; 1277 buf_writer.writeByte(unit.sep) catch unreachable; 1278 ns_remaining -= units * unit.ns; 1279 if (ns_remaining == 0) 1280 return formatBuf(fbs.getWritten(), options, writer); 1281 } 1282 } 1283 1284 inline for (.{ 1285 .{ .ns = std.time.ns_per_s, .sep = "s" }, 1286 .{ .ns = std.time.ns_per_ms, .sep = "ms" }, 1287 .{ .ns = std.time.ns_per_us, .sep = "us" }, 1288 }) |unit| { 1289 const kunits = ns_remaining * 1000 / unit.ns; 1290 if (kunits >= 1000) { 1291 formatInt(kunits / 1000, 10, .lower, .{}, buf_writer) catch unreachable; 1292 const frac = kunits % 1000; 1293 if (frac > 0) { 1294 // Write up to 3 decimal places 1295 var decimal_buf = [_]u8{ '.', 0, 0, 0 }; 1296 _ = formatIntBuf(decimal_buf[1..], frac, 10, .lower, .{ .fill = '0', .width = 3 }); 1297 var end: usize = 4; 1298 while (end > 1) : (end -= 1) { 1299 if (decimal_buf[end - 1] != '0') break; 1300 } 1301 buf_writer.writeAll(decimal_buf[0..end]) catch unreachable; 1302 } 1303 buf_writer.writeAll(unit.sep) catch unreachable; 1304 return formatBuf(fbs.getWritten(), options, writer); 1305 } 1306 } 1307 1308 formatInt(ns_remaining, 10, .lower, .{}, buf_writer) catch unreachable; 1309 buf_writer.writeAll("ns") catch unreachable; 1310 return formatBuf(fbs.getWritten(), options, writer); 1311 } 1312 1313 /// Return a Formatter for number of nanoseconds according to its magnitude: 1314 /// [#y][#w][#d][#h][#m]#[.###][n|u|m]s 1315 pub fn fmtDuration(ns: u64) Formatter(formatDuration) { 1316 const data = FormatDurationData{ .ns = ns }; 1317 return .{ .data = data }; 1318 } 1319 1320 test fmtDuration { 1321 var buf: [24]u8 = undefined; 1322 inline for (.{ 1323 .{ .s = "0ns", .d = 0 }, 1324 .{ .s = "1ns", .d = 1 }, 1325 .{ .s = "999ns", .d = std.time.ns_per_us - 1 }, 1326 .{ .s = "1us", .d = std.time.ns_per_us }, 1327 .{ .s = "1.45us", .d = 1450 }, 1328 .{ .s = "1.5us", .d = 3 * std.time.ns_per_us / 2 }, 1329 .{ .s = "14.5us", .d = 14500 }, 1330 .{ .s = "145us", .d = 145000 }, 1331 .{ .s = "999.999us", .d = std.time.ns_per_ms - 1 }, 1332 .{ .s = "1ms", .d = std.time.ns_per_ms + 1 }, 1333 .{ .s = "1.5ms", .d = 3 * std.time.ns_per_ms / 2 }, 1334 .{ .s = "1.11ms", .d = 1110000 }, 1335 .{ .s = "1.111ms", .d = 1111000 }, 1336 .{ .s = "1.111ms", .d = 1111100 }, 1337 .{ .s = "999.999ms", .d = std.time.ns_per_s - 1 }, 1338 .{ .s = "1s", .d = std.time.ns_per_s }, 1339 .{ .s = "59.999s", .d = std.time.ns_per_min - 1 }, 1340 .{ .s = "1m", .d = std.time.ns_per_min }, 1341 .{ .s = "1h", .d = std.time.ns_per_hour }, 1342 .{ .s = "1d", .d = std.time.ns_per_day }, 1343 .{ .s = "1w", .d = std.time.ns_per_week }, 1344 .{ .s = "1y", .d = 365 * std.time.ns_per_day }, 1345 .{ .s = "1y52w23h59m59.999s", .d = 730 * std.time.ns_per_day - 1 }, // 365d = 52w1d 1346 .{ .s = "1y1h1.001s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms }, 1347 .{ .s = "1y1h1s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us }, 1348 .{ .s = "1y1h999.999us", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1 }, 1349 .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms }, 1350 .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1 }, 1351 .{ .s = "1y1m999ns", .d = 365 * std.time.ns_per_day + std.time.ns_per_min + 999 }, 1352 .{ .s = "584y49w23h34m33.709s", .d = math.maxInt(u64) }, 1353 }) |tc| { 1354 const slice = try bufPrint(&buf, "{}", .{fmtDuration(tc.d)}); 1355 try std.testing.expectEqualStrings(tc.s, slice); 1356 } 1357 1358 inline for (.{ 1359 .{ .s = "=======0ns", .f = "{s:=>10}", .d = 0 }, 1360 .{ .s = "1ns=======", .f = "{s:=<10}", .d = 1 }, 1361 .{ .s = " 999ns ", .f = "{s:^10}", .d = std.time.ns_per_us - 1 }, 1362 }) |tc| { 1363 const slice = try bufPrint(&buf, tc.f, .{fmtDuration(tc.d)}); 1364 try std.testing.expectEqualStrings(tc.s, slice); 1365 } 1366 } 1367 1368 fn formatDurationSigned(ns: i64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1369 if (ns < 0) { 1370 const data = FormatDurationData{ .ns = @as(u64, @intCast(-ns)), .negative = true }; 1371 try formatDuration(data, fmt, options, writer); 1372 } else { 1373 const data = FormatDurationData{ .ns = @as(u64, @intCast(ns)) }; 1374 try formatDuration(data, fmt, options, writer); 1375 } 1376 } 1377 1378 /// Return a Formatter for number of nanoseconds according to its signed magnitude: 1379 /// [#y][#w][#d][#h][#m]#[.###][n|u|m]s 1380 pub fn fmtDurationSigned(ns: i64) Formatter(formatDurationSigned) { 1381 return .{ .data = ns }; 1382 } 1383 1384 test fmtDurationSigned { 1385 var buf: [24]u8 = undefined; 1386 inline for (.{ 1387 .{ .s = "0ns", .d = 0 }, 1388 .{ .s = "1ns", .d = 1 }, 1389 .{ .s = "-1ns", .d = -(1) }, 1390 .{ .s = "999ns", .d = std.time.ns_per_us - 1 }, 1391 .{ .s = "-999ns", .d = -(std.time.ns_per_us - 1) }, 1392 .{ .s = "1us", .d = std.time.ns_per_us }, 1393 .{ .s = "-1us", .d = -(std.time.ns_per_us) }, 1394 .{ .s = "1.45us", .d = 1450 }, 1395 .{ .s = "-1.45us", .d = -(1450) }, 1396 .{ .s = "1.5us", .d = 3 * std.time.ns_per_us / 2 }, 1397 .{ .s = "-1.5us", .d = -(3 * std.time.ns_per_us / 2) }, 1398 .{ .s = "14.5us", .d = 14500 }, 1399 .{ .s = "-14.5us", .d = -(14500) }, 1400 .{ .s = "145us", .d = 145000 }, 1401 .{ .s = "-145us", .d = -(145000) }, 1402 .{ .s = "999.999us", .d = std.time.ns_per_ms - 1 }, 1403 .{ .s = "-999.999us", .d = -(std.time.ns_per_ms - 1) }, 1404 .{ .s = "1ms", .d = std.time.ns_per_ms + 1 }, 1405 .{ .s = "-1ms", .d = -(std.time.ns_per_ms + 1) }, 1406 .{ .s = "1.5ms", .d = 3 * std.time.ns_per_ms / 2 }, 1407 .{ .s = "-1.5ms", .d = -(3 * std.time.ns_per_ms / 2) }, 1408 .{ .s = "1.11ms", .d = 1110000 }, 1409 .{ .s = "-1.11ms", .d = -(1110000) }, 1410 .{ .s = "1.111ms", .d = 1111000 }, 1411 .{ .s = "-1.111ms", .d = -(1111000) }, 1412 .{ .s = "1.111ms", .d = 1111100 }, 1413 .{ .s = "-1.111ms", .d = -(1111100) }, 1414 .{ .s = "999.999ms", .d = std.time.ns_per_s - 1 }, 1415 .{ .s = "-999.999ms", .d = -(std.time.ns_per_s - 1) }, 1416 .{ .s = "1s", .d = std.time.ns_per_s }, 1417 .{ .s = "-1s", .d = -(std.time.ns_per_s) }, 1418 .{ .s = "59.999s", .d = std.time.ns_per_min - 1 }, 1419 .{ .s = "-59.999s", .d = -(std.time.ns_per_min - 1) }, 1420 .{ .s = "1m", .d = std.time.ns_per_min }, 1421 .{ .s = "-1m", .d = -(std.time.ns_per_min) }, 1422 .{ .s = "1h", .d = std.time.ns_per_hour }, 1423 .{ .s = "-1h", .d = -(std.time.ns_per_hour) }, 1424 .{ .s = "1d", .d = std.time.ns_per_day }, 1425 .{ .s = "-1d", .d = -(std.time.ns_per_day) }, 1426 .{ .s = "1w", .d = std.time.ns_per_week }, 1427 .{ .s = "-1w", .d = -(std.time.ns_per_week) }, 1428 .{ .s = "1y", .d = 365 * std.time.ns_per_day }, 1429 .{ .s = "-1y", .d = -(365 * std.time.ns_per_day) }, 1430 .{ .s = "1y52w23h59m59.999s", .d = 730 * std.time.ns_per_day - 1 }, // 365d = 52w1d 1431 .{ .s = "-1y52w23h59m59.999s", .d = -(730 * std.time.ns_per_day - 1) }, // 365d = 52w1d 1432 .{ .s = "1y1h1.001s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms }, 1433 .{ .s = "-1y1h1.001s", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms) }, 1434 .{ .s = "1y1h1s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us }, 1435 .{ .s = "-1y1h1s", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us) }, 1436 .{ .s = "1y1h999.999us", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1 }, 1437 .{ .s = "-1y1h999.999us", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1) }, 1438 .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms }, 1439 .{ .s = "-1y1h1ms", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms) }, 1440 .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1 }, 1441 .{ .s = "-1y1h1ms", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1) }, 1442 .{ .s = "1y1m999ns", .d = 365 * std.time.ns_per_day + std.time.ns_per_min + 999 }, 1443 .{ .s = "-1y1m999ns", .d = -(365 * std.time.ns_per_day + std.time.ns_per_min + 999) }, 1444 .{ .s = "292y24w3d23h47m16.854s", .d = math.maxInt(i64) }, 1445 .{ .s = "-292y24w3d23h47m16.854s", .d = math.minInt(i64) + 1 }, 1446 }) |tc| { 1447 const slice = try bufPrint(&buf, "{}", .{fmtDurationSigned(tc.d)}); 1448 try std.testing.expectEqualStrings(tc.s, slice); 1449 } 1450 1451 inline for (.{ 1452 .{ .s = "=======0ns", .f = "{s:=>10}", .d = 0 }, 1453 .{ .s = "1ns=======", .f = "{s:=<10}", .d = 1 }, 1454 .{ .s = "-1ns======", .f = "{s:=<10}", .d = -(1) }, 1455 .{ .s = " -999ns ", .f = "{s:^10}", .d = -(std.time.ns_per_us - 1) }, 1456 }) |tc| { 1457 const slice = try bufPrint(&buf, tc.f, .{fmtDurationSigned(tc.d)}); 1458 try std.testing.expectEqualStrings(tc.s, slice); 1459 } 1460 } 1461 1462 pub const ParseIntError = error{ 1463 /// The result cannot fit in the type specified 1464 Overflow, 1465 1466 /// The input was empty or contained an invalid character 1467 InvalidCharacter, 1468 }; 1469 1470 /// Creates a Formatter type from a format function. Wrapping data in Formatter(func) causes 1471 /// the data to be formatted using the given function `func`. `func` must be of the following 1472 /// form: 1473 /// 1474 /// fn formatExample( 1475 /// data: T, 1476 /// comptime fmt: []const u8, 1477 /// options: std.fmt.FormatOptions, 1478 /// writer: anytype, 1479 /// ) !void; 1480 /// 1481 pub fn Formatter(comptime formatFn: anytype) type { 1482 const Data = @typeInfo(@TypeOf(formatFn)).@"fn".params[0].type.?; 1483 return struct { 1484 data: Data, 1485 pub fn format( 1486 self: @This(), 1487 comptime fmt: []const u8, 1488 options: std.fmt.FormatOptions, 1489 writer: anytype, 1490 ) @TypeOf(writer).Error!void { 1491 try formatFn(self.data, fmt, options, writer); 1492 } 1493 }; 1494 } 1495 1496 /// Parses the string `buf` as signed or unsigned representation in the 1497 /// specified base of an integral value of type `T`. 1498 /// 1499 /// When `base` is zero the string prefix is examined to detect the true base: 1500 /// * A prefix of "0b" implies base=2, 1501 /// * A prefix of "0o" implies base=8, 1502 /// * A prefix of "0x" implies base=16, 1503 /// * Otherwise base=10 is assumed. 1504 /// 1505 /// Ignores '_' character in `buf`. 1506 /// See also `parseUnsigned`. 1507 pub fn parseInt(comptime T: type, buf: []const u8, base: u8) ParseIntError!T { 1508 return parseIntWithGenericCharacter(T, u8, buf, base); 1509 } 1510 1511 /// Like `parseInt`, but with a generic `Character` type. 1512 pub fn parseIntWithGenericCharacter( 1513 comptime Result: type, 1514 comptime Character: type, 1515 buf: []const Character, 1516 base: u8, 1517 ) ParseIntError!Result { 1518 if (buf.len == 0) return error.InvalidCharacter; 1519 if (buf[0] == '+') return parseIntWithSign(Result, Character, buf[1..], base, .pos); 1520 if (buf[0] == '-') return parseIntWithSign(Result, Character, buf[1..], base, .neg); 1521 return parseIntWithSign(Result, Character, buf, base, .pos); 1522 } 1523 1524 test parseInt { 1525 try std.testing.expectEqual(-10, try parseInt(i32, "-10", 10)); 1526 try std.testing.expectEqual(10, try parseInt(i32, "+10", 10)); 1527 try std.testing.expectEqual(10, try parseInt(u32, "+10", 10)); 1528 try std.testing.expectError(error.Overflow, parseInt(u32, "-10", 10)); 1529 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, " 10", 10)); 1530 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "10 ", 10)); 1531 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "_10_", 10)); 1532 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0x_10_", 10)); 1533 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0x10_", 10)); 1534 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0x_10", 10)); 1535 try std.testing.expectEqual(255, try parseInt(u8, "255", 10)); 1536 try std.testing.expectError(error.Overflow, parseInt(u8, "256", 10)); 1537 1538 // +0 and -0 should work for unsigned 1539 try std.testing.expectEqual(0, try parseInt(u8, "-0", 10)); 1540 try std.testing.expectEqual(0, try parseInt(u8, "+0", 10)); 1541 1542 // ensure minInt is parsed correctly 1543 try std.testing.expectEqual(math.minInt(i1), try parseInt(i1, "-1", 10)); 1544 try std.testing.expectEqual(math.minInt(i8), try parseInt(i8, "-128", 10)); 1545 try std.testing.expectEqual(math.minInt(i43), try parseInt(i43, "-4398046511104", 10)); 1546 1547 // empty string or bare +- is invalid 1548 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "", 10)); 1549 try std.testing.expectError(error.InvalidCharacter, parseInt(i32, "", 10)); 1550 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "+", 10)); 1551 try std.testing.expectError(error.InvalidCharacter, parseInt(i32, "+", 10)); 1552 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "-", 10)); 1553 try std.testing.expectError(error.InvalidCharacter, parseInt(i32, "-", 10)); 1554 1555 // autodectect the base 1556 try std.testing.expectEqual(111, try parseInt(i32, "111", 0)); 1557 try std.testing.expectEqual(111, try parseInt(i32, "1_1_1", 0)); 1558 try std.testing.expectEqual(111, try parseInt(i32, "1_1_1", 0)); 1559 try std.testing.expectEqual(7, try parseInt(i32, "+0b111", 0)); 1560 try std.testing.expectEqual(7, try parseInt(i32, "+0B111", 0)); 1561 try std.testing.expectEqual(7, try parseInt(i32, "+0b1_11", 0)); 1562 try std.testing.expectEqual(73, try parseInt(i32, "+0o111", 0)); 1563 try std.testing.expectEqual(73, try parseInt(i32, "+0O111", 0)); 1564 try std.testing.expectEqual(73, try parseInt(i32, "+0o11_1", 0)); 1565 try std.testing.expectEqual(273, try parseInt(i32, "+0x111", 0)); 1566 try std.testing.expectEqual(-7, try parseInt(i32, "-0b111", 0)); 1567 try std.testing.expectEqual(-7, try parseInt(i32, "-0b11_1", 0)); 1568 try std.testing.expectEqual(-73, try parseInt(i32, "-0o111", 0)); 1569 try std.testing.expectEqual(-273, try parseInt(i32, "-0x111", 0)); 1570 try std.testing.expectEqual(-273, try parseInt(i32, "-0X111", 0)); 1571 try std.testing.expectEqual(-273, try parseInt(i32, "-0x1_11", 0)); 1572 1573 // bare binary/octal/decimal prefix is invalid 1574 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0b", 0)); 1575 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0o", 0)); 1576 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0x", 0)); 1577 1578 // edge cases which previously errored due to base overflowing T 1579 try std.testing.expectEqual(@as(i2, -2), try std.fmt.parseInt(i2, "-10", 2)); 1580 try std.testing.expectEqual(@as(i4, -8), try std.fmt.parseInt(i4, "-10", 8)); 1581 try std.testing.expectEqual(@as(i5, -16), try std.fmt.parseInt(i5, "-10", 16)); 1582 } 1583 1584 fn parseIntWithSign( 1585 comptime Result: type, 1586 comptime Character: type, 1587 buf: []const Character, 1588 base: u8, 1589 comptime sign: enum { pos, neg }, 1590 ) ParseIntError!Result { 1591 if (buf.len == 0) return error.InvalidCharacter; 1592 1593 var buf_base = base; 1594 var buf_start = buf; 1595 if (base == 0) { 1596 // Treat is as a decimal number by default. 1597 buf_base = 10; 1598 // Detect the base by looking at buf prefix. 1599 if (buf.len > 2 and buf[0] == '0') { 1600 if (math.cast(u8, buf[1])) |c| switch (std.ascii.toLower(c)) { 1601 'b' => { 1602 buf_base = 2; 1603 buf_start = buf[2..]; 1604 }, 1605 'o' => { 1606 buf_base = 8; 1607 buf_start = buf[2..]; 1608 }, 1609 'x' => { 1610 buf_base = 16; 1611 buf_start = buf[2..]; 1612 }, 1613 else => {}, 1614 }; 1615 } 1616 } 1617 1618 const add = switch (sign) { 1619 .pos => math.add, 1620 .neg => math.sub, 1621 }; 1622 1623 // accumulate into Accumulate which is always 8 bits or larger. this prevents 1624 // `buf_base` from overflowing Result. 1625 const info = @typeInfo(Result); 1626 const Accumulate = std.meta.Int(info.int.signedness, @max(8, info.int.bits)); 1627 var accumulate: Accumulate = 0; 1628 1629 if (buf_start[0] == '_' or buf_start[buf_start.len - 1] == '_') return error.InvalidCharacter; 1630 1631 for (buf_start) |c| { 1632 if (c == '_') continue; 1633 const digit = try charToDigit(math.cast(u8, c) orelse return error.InvalidCharacter, buf_base); 1634 if (accumulate != 0) { 1635 accumulate = try math.mul(Accumulate, accumulate, math.cast(Accumulate, buf_base) orelse return error.Overflow); 1636 } else if (sign == .neg) { 1637 // The first digit of a negative number. 1638 // Consider parsing "-4" as an i3. 1639 // This should work, but positive 4 overflows i3, so we can't cast the digit to T and subtract. 1640 accumulate = math.cast(Accumulate, -@as(i8, @intCast(digit))) orelse return error.Overflow; 1641 continue; 1642 } 1643 accumulate = try add(Accumulate, accumulate, math.cast(Accumulate, digit) orelse return error.Overflow); 1644 } 1645 1646 return if (Result == Accumulate) 1647 accumulate 1648 else 1649 math.cast(Result, accumulate) orelse return error.Overflow; 1650 } 1651 1652 /// Parses the string `buf` as unsigned representation in the specified base 1653 /// of an integral value of type `T`. 1654 /// 1655 /// When `base` is zero the string prefix is examined to detect the true base: 1656 /// * A prefix of "0b" implies base=2, 1657 /// * A prefix of "0o" implies base=8, 1658 /// * A prefix of "0x" implies base=16, 1659 /// * Otherwise base=10 is assumed. 1660 /// 1661 /// Ignores '_' character in `buf`. 1662 /// See also `parseInt`. 1663 pub fn parseUnsigned(comptime T: type, buf: []const u8, base: u8) ParseIntError!T { 1664 return parseIntWithSign(T, u8, buf, base, .pos); 1665 } 1666 1667 test parseUnsigned { 1668 try std.testing.expectEqual(50124, try parseUnsigned(u16, "050124", 10)); 1669 try std.testing.expectEqual(65535, try parseUnsigned(u16, "65535", 10)); 1670 try std.testing.expectEqual(65535, try parseUnsigned(u16, "65_535", 10)); 1671 try std.testing.expectError(error.Overflow, parseUnsigned(u16, "65536", 10)); 1672 1673 try std.testing.expectEqual(0xffffffffffffffff, try parseUnsigned(u64, "0ffffffffffffffff", 16)); 1674 try std.testing.expectEqual(0xffffffffffffffff, try parseUnsigned(u64, "0f_fff_fff_fff_fff_fff", 16)); 1675 try std.testing.expectError(error.Overflow, parseUnsigned(u64, "10000000000000000", 16)); 1676 1677 try std.testing.expectEqual(0xDEADBEEF, try parseUnsigned(u32, "DeadBeef", 16)); 1678 1679 try std.testing.expectEqual(1, try parseUnsigned(u7, "1", 10)); 1680 try std.testing.expectEqual(8, try parseUnsigned(u7, "1000", 2)); 1681 1682 try std.testing.expectError(error.InvalidCharacter, parseUnsigned(u32, "f", 10)); 1683 try std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "109", 8)); 1684 1685 try std.testing.expectEqual(1442151747, try parseUnsigned(u32, "NUMBER", 36)); 1686 1687 // these numbers should fit even though the base itself doesn't fit in the destination type 1688 try std.testing.expectEqual(0, try parseUnsigned(u1, "0", 10)); 1689 try std.testing.expectEqual(1, try parseUnsigned(u1, "1", 10)); 1690 try std.testing.expectError(error.Overflow, parseUnsigned(u1, "2", 10)); 1691 try std.testing.expectEqual(1, try parseUnsigned(u1, "001", 16)); 1692 try std.testing.expectEqual(3, try parseUnsigned(u2, "3", 16)); 1693 try std.testing.expectError(error.Overflow, parseUnsigned(u2, "4", 16)); 1694 1695 // parseUnsigned does not expect a sign 1696 try std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "+0", 10)); 1697 try std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "-0", 10)); 1698 1699 // test empty string error 1700 try std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "", 10)); 1701 } 1702 1703 /// Parses a number like '2G', '2Gi', or '2GiB'. 1704 pub fn parseIntSizeSuffix(buf: []const u8, digit_base: u8) ParseIntError!usize { 1705 var without_B = buf; 1706 if (mem.endsWith(u8, buf, "B")) without_B.len -= 1; 1707 var without_i = without_B; 1708 var magnitude_base: usize = 1000; 1709 if (mem.endsWith(u8, without_B, "i")) { 1710 without_i.len -= 1; 1711 magnitude_base = 1024; 1712 } 1713 if (without_i.len == 0) return error.InvalidCharacter; 1714 const orders_of_magnitude: usize = switch (without_i[without_i.len - 1]) { 1715 'k', 'K' => 1, 1716 'M' => 2, 1717 'G' => 3, 1718 'T' => 4, 1719 'P' => 5, 1720 'E' => 6, 1721 'Z' => 7, 1722 'Y' => 8, 1723 'R' => 9, 1724 'Q' => 10, 1725 else => 0, 1726 }; 1727 var without_suffix = without_i; 1728 if (orders_of_magnitude > 0) { 1729 without_suffix.len -= 1; 1730 } else if (without_i.len != without_B.len) { 1731 return error.InvalidCharacter; 1732 } 1733 const multiplier = math.powi(usize, magnitude_base, orders_of_magnitude) catch |err| switch (err) { 1734 error.Underflow => unreachable, 1735 error.Overflow => return error.Overflow, 1736 }; 1737 const number = try std.fmt.parseInt(usize, without_suffix, digit_base); 1738 return math.mul(usize, number, multiplier); 1739 } 1740 1741 test parseIntSizeSuffix { 1742 try std.testing.expectEqual(2, try parseIntSizeSuffix("2", 10)); 1743 try std.testing.expectEqual(2, try parseIntSizeSuffix("2B", 10)); 1744 try std.testing.expectEqual(2000, try parseIntSizeSuffix("2kB", 10)); 1745 try std.testing.expectEqual(2000, try parseIntSizeSuffix("2k", 10)); 1746 try std.testing.expectEqual(2048, try parseIntSizeSuffix("2KiB", 10)); 1747 try std.testing.expectEqual(2048, try parseIntSizeSuffix("2Ki", 10)); 1748 try std.testing.expectEqual(10240, try parseIntSizeSuffix("aKiB", 16)); 1749 try std.testing.expectError(error.InvalidCharacter, parseIntSizeSuffix("", 10)); 1750 try std.testing.expectError(error.InvalidCharacter, parseIntSizeSuffix("2iB", 10)); 1751 } 1752 1753 pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; 1754 pub const ParseFloatError = @import("fmt/parse_float.zig").ParseFloatError; 1755 1756 test { 1757 _ = &parseFloat; 1758 } 1759 1760 pub fn charToDigit(c: u8, base: u8) (error{InvalidCharacter}!u8) { 1761 const value = switch (c) { 1762 '0'...'9' => c - '0', 1763 'A'...'Z' => c - 'A' + 10, 1764 'a'...'z' => c - 'a' + 10, 1765 else => return error.InvalidCharacter, 1766 }; 1767 1768 if (value >= base) return error.InvalidCharacter; 1769 1770 return value; 1771 } 1772 1773 pub fn digitToChar(digit: u8, case: Case) u8 { 1774 return switch (digit) { 1775 0...9 => digit + '0', 1776 10...35 => digit + ((if (case == .upper) @as(u8, 'A') else @as(u8, 'a')) - 10), 1777 else => unreachable, 1778 }; 1779 } 1780 1781 pub const BufPrintError = error{ 1782 /// As much as possible was written to the buffer, but it was too small to fit all the printed bytes. 1783 NoSpaceLeft, 1784 }; 1785 1786 /// Print a Formatter string into `buf`. Actually just a thin wrapper around `format` and `fixedBufferStream`. 1787 /// Returns a slice of the bytes printed to. 1788 pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintError![]u8 { 1789 var fbs = std.io.fixedBufferStream(buf); 1790 format(fbs.writer().any(), fmt, args) catch |err| switch (err) { 1791 error.NoSpaceLeft => return error.NoSpaceLeft, 1792 else => unreachable, 1793 }; 1794 return fbs.getWritten(); 1795 } 1796 1797 pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintError![:0]u8 { 1798 const result = try bufPrint(buf, fmt ++ "\x00", args); 1799 return result[0 .. result.len - 1 :0]; 1800 } 1801 1802 /// Count the characters needed for format. Useful for preallocating memory 1803 pub fn count(comptime fmt: []const u8, args: anytype) u64 { 1804 var counting_writer = std.io.countingWriter(std.io.null_writer); 1805 format(counting_writer.writer().any(), fmt, args) catch unreachable; 1806 return counting_writer.bytes_written; 1807 } 1808 1809 pub const AllocPrintError = error{OutOfMemory}; 1810 1811 pub fn allocPrint(allocator: mem.Allocator, comptime fmt: []const u8, args: anytype) AllocPrintError![]u8 { 1812 const size = math.cast(usize, count(fmt, args)) orelse return error.OutOfMemory; 1813 const buf = try allocator.alloc(u8, size); 1814 return bufPrint(buf, fmt, args) catch |err| switch (err) { 1815 error.NoSpaceLeft => unreachable, // we just counted the size above 1816 }; 1817 } 1818 1819 pub fn allocPrintZ(allocator: mem.Allocator, comptime fmt: []const u8, args: anytype) AllocPrintError![:0]u8 { 1820 const result = try allocPrint(allocator, fmt ++ "\x00", args); 1821 return result[0 .. result.len - 1 :0]; 1822 } 1823 1824 test bufPrintIntToSlice { 1825 var buffer: [100]u8 = undefined; 1826 const buf = buffer[0..]; 1827 1828 try std.testing.expectEqualSlices(u8, "-1", bufPrintIntToSlice(buf, @as(i1, -1), 10, .lower, FormatOptions{})); 1829 1830 try std.testing.expectEqualSlices(u8, "-101111000110000101001110", bufPrintIntToSlice(buf, @as(i32, -12345678), 2, .lower, FormatOptions{})); 1831 try std.testing.expectEqualSlices(u8, "-12345678", bufPrintIntToSlice(buf, @as(i32, -12345678), 10, .lower, FormatOptions{})); 1832 try std.testing.expectEqualSlices(u8, "-bc614e", bufPrintIntToSlice(buf, @as(i32, -12345678), 16, .lower, FormatOptions{})); 1833 try std.testing.expectEqualSlices(u8, "-BC614E", bufPrintIntToSlice(buf, @as(i32, -12345678), 16, .upper, FormatOptions{})); 1834 1835 try std.testing.expectEqualSlices(u8, "12345678", bufPrintIntToSlice(buf, @as(u32, 12345678), 10, .upper, FormatOptions{})); 1836 1837 try std.testing.expectEqualSlices(u8, " 666", bufPrintIntToSlice(buf, @as(u32, 666), 10, .lower, FormatOptions{ .width = 6 })); 1838 try std.testing.expectEqualSlices(u8, " 1234", bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, .lower, FormatOptions{ .width = 6 })); 1839 try std.testing.expectEqualSlices(u8, "1234", bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, .lower, FormatOptions{ .width = 1 })); 1840 1841 try std.testing.expectEqualSlices(u8, "+42", bufPrintIntToSlice(buf, @as(i32, 42), 10, .lower, FormatOptions{ .width = 3 })); 1842 try std.testing.expectEqualSlices(u8, "-42", bufPrintIntToSlice(buf, @as(i32, -42), 10, .lower, FormatOptions{ .width = 3 })); 1843 } 1844 1845 pub fn bufPrintIntToSlice(buf: []u8, value: anytype, base: u8, case: Case, options: FormatOptions) []u8 { 1846 return buf[0..formatIntBuf(buf, value, base, case, options)]; 1847 } 1848 1849 pub inline fn comptimePrint(comptime fmt: []const u8, args: anytype) *const [count(fmt, args):0]u8 { 1850 comptime { 1851 var buf: [count(fmt, args):0]u8 = undefined; 1852 _ = bufPrint(&buf, fmt, args) catch unreachable; 1853 buf[buf.len] = 0; 1854 const final = buf; 1855 return &final; 1856 } 1857 } 1858 1859 test comptimePrint { 1860 @setEvalBranchQuota(2000); 1861 try std.testing.expectEqual(*const [3:0]u8, @TypeOf(comptimePrint("{}", .{100}))); 1862 try std.testing.expectEqualSlices(u8, "100", comptimePrint("{}", .{100})); 1863 try std.testing.expectEqualStrings("30", comptimePrint("{d}", .{30.0})); 1864 try std.testing.expectEqualStrings("30.0", comptimePrint("{d:3.1}", .{30.0})); 1865 try std.testing.expectEqualStrings("0.05", comptimePrint("{d}", .{0.05})); 1866 try std.testing.expectEqualStrings("5e-2", comptimePrint("{e}", .{0.05})); 1867 } 1868 1869 test "parse u64 digit too big" { 1870 _ = parseUnsigned(u64, "123a", 10) catch |err| { 1871 if (err == error.InvalidCharacter) return; 1872 unreachable; 1873 }; 1874 unreachable; 1875 } 1876 1877 test "parse unsigned comptime" { 1878 comptime { 1879 try std.testing.expectEqual(2, try parseUnsigned(usize, "2", 10)); 1880 } 1881 } 1882 1883 test "escaped braces" { 1884 try expectFmt("escaped: {{foo}}\n", "escaped: {{{{foo}}}}\n", .{}); 1885 try expectFmt("escaped: {foo}\n", "escaped: {{foo}}\n", .{}); 1886 } 1887 1888 test "optional" { 1889 { 1890 const value: ?i32 = 1234; 1891 try expectFmt("optional: 1234\n", "optional: {?}\n", .{value}); 1892 try expectFmt("optional: 1234\n", "optional: {?d}\n", .{value}); 1893 try expectFmt("optional: 4d2\n", "optional: {?x}\n", .{value}); 1894 } 1895 { 1896 const value: ?[]const u8 = "string"; 1897 try expectFmt("optional: string\n", "optional: {?s}\n", .{value}); 1898 } 1899 { 1900 const value: ?i32 = null; 1901 try expectFmt("optional: null\n", "optional: {?}\n", .{value}); 1902 } 1903 { 1904 const value = @as(?*i32, @ptrFromInt(0xf000d000)); 1905 try expectFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value}); 1906 } 1907 } 1908 1909 test "error" { 1910 { 1911 const value: anyerror!i32 = 1234; 1912 try expectFmt("error union: 1234\n", "error union: {!}\n", .{value}); 1913 try expectFmt("error union: 1234\n", "error union: {!d}\n", .{value}); 1914 try expectFmt("error union: 4d2\n", "error union: {!x}\n", .{value}); 1915 } 1916 { 1917 const value: anyerror![]const u8 = "string"; 1918 try expectFmt("error union: string\n", "error union: {!s}\n", .{value}); 1919 } 1920 { 1921 const value: anyerror!i32 = error.InvalidChar; 1922 try expectFmt("error union: error.InvalidChar\n", "error union: {!}\n", .{value}); 1923 } 1924 } 1925 1926 test "int.small" { 1927 { 1928 const value: u3 = 0b101; 1929 try expectFmt("u3: 5\n", "u3: {}\n", .{value}); 1930 } 1931 } 1932 1933 test "int.specifier" { 1934 { 1935 const value: u8 = 'a'; 1936 try expectFmt("u8: a\n", "u8: {c}\n", .{value}); 1937 } 1938 { 1939 const value: u8 = 0b1100; 1940 try expectFmt("u8: 0b1100\n", "u8: 0b{b}\n", .{value}); 1941 } 1942 { 1943 const value: u16 = 0o1234; 1944 try expectFmt("u16: 0o1234\n", "u16: 0o{o}\n", .{value}); 1945 } 1946 { 1947 const value: u8 = 'a'; 1948 try expectFmt("UTF-8: a\n", "UTF-8: {u}\n", .{value}); 1949 } 1950 { 1951 const value: u21 = 0x1F310; 1952 try expectFmt("UTF-8: 🌐\n", "UTF-8: {u}\n", .{value}); 1953 } 1954 { 1955 const value: u21 = 0xD800; 1956 try expectFmt("UTF-8: �\n", "UTF-8: {u}\n", .{value}); 1957 } 1958 { 1959 const value: u21 = 0x110001; 1960 try expectFmt("UTF-8: �\n", "UTF-8: {u}\n", .{value}); 1961 } 1962 } 1963 1964 test "int.padded" { 1965 try expectFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); 1966 try expectFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)}); 1967 try expectFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)}); 1968 try expectFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)}); 1969 try expectFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)}); 1970 try expectFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)}); 1971 try expectFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)}); 1972 try expectFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)}); 1973 try expectFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)}); 1974 try expectFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)}); 1975 try expectFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)}); 1976 try expectFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)}); 1977 1978 try expectFmt("UTF-8: 'ü '", "UTF-8: '{u:<4}'", .{'ü'}); 1979 try expectFmt("UTF-8: ' ü'", "UTF-8: '{u:>4}'", .{'ü'}); 1980 try expectFmt("UTF-8: ' ü '", "UTF-8: '{u:^4}'", .{'ü'}); 1981 } 1982 1983 test "buffer" { 1984 { 1985 var buf1: [32]u8 = undefined; 1986 var fbs = std.io.fixedBufferStream(&buf1); 1987 try formatType(1234, "", FormatOptions{}, fbs.writer(), std.options.fmt_max_depth); 1988 try std.testing.expectEqualStrings("1234", fbs.getWritten()); 1989 1990 fbs.reset(); 1991 try formatType('a', "c", FormatOptions{}, fbs.writer(), std.options.fmt_max_depth); 1992 try std.testing.expectEqualStrings("a", fbs.getWritten()); 1993 1994 fbs.reset(); 1995 try formatType(0b1100, "b", FormatOptions{}, fbs.writer(), std.options.fmt_max_depth); 1996 try std.testing.expectEqualStrings("1100", fbs.getWritten()); 1997 } 1998 } 1999 2000 // Test formatting of arrays by value, by single-item pointer, and as a slice 2001 fn expectArrayFmt(expected: []const u8, comptime template: []const u8, comptime array_value: anytype) !void { 2002 try expectFmt(expected, template, .{array_value}); 2003 try expectFmt(expected, template, .{&array_value}); 2004 var runtime_zero: usize = 0; 2005 _ = &runtime_zero; 2006 try expectFmt(expected, template, .{array_value[runtime_zero..]}); 2007 } 2008 2009 test "array" { 2010 { 2011 const value: [3]u8 = "abc".*; 2012 try expectArrayFmt("array: abc\n", "array: {s}\n", value); 2013 try expectArrayFmt("array: { 97, 98, 99 }\n", "array: {d}\n", value); 2014 try expectArrayFmt("array: { 61, 62, 63 }\n", "array: {x}\n", value); 2015 try expectArrayFmt("array: { 97, 98, 99 }\n", "array: {any}\n", value); 2016 2017 var buf: [100]u8 = undefined; 2018 try expectFmt( 2019 try bufPrint(buf[0..], "array: [3]u8@{x}\n", .{@intFromPtr(&value)}), 2020 "array: {*}\n", 2021 .{&value}, 2022 ); 2023 } 2024 2025 { 2026 const value = [2][3]u8{ "abc".*, "def".* }; 2027 2028 try expectArrayFmt("array: { abc, def }\n", "array: {s}\n", value); 2029 try expectArrayFmt("array: { { 97, 98, 99 }, { 100, 101, 102 } }\n", "array: {d}\n", value); 2030 try expectArrayFmt("array: { { 61, 62, 63 }, { 64, 65, 66 } }\n", "array: {x}\n", value); 2031 } 2032 } 2033 2034 test "slice" { 2035 { 2036 const value: []const u8 = "abc"; 2037 try expectFmt("slice: abc\n", "slice: {s}\n", .{value}); 2038 try expectFmt("slice: { 97, 98, 99 }\n", "slice: {d}\n", .{value}); 2039 try expectFmt("slice: { 61, 62, 63 }\n", "slice: {x}\n", .{value}); 2040 try expectFmt("slice: { 97, 98, 99 }\n", "slice: {any}\n", .{value}); 2041 } 2042 { 2043 var runtime_zero: usize = 0; 2044 _ = &runtime_zero; 2045 const value = @as([*]align(1) const []const u8, @ptrFromInt(0xdeadbeef))[runtime_zero..runtime_zero]; 2046 try expectFmt("slice: []const u8@deadbeef\n", "slice: {*}\n", .{value}); 2047 } 2048 { 2049 const null_term_slice: [:0]const u8 = "\x00hello\x00"; 2050 try expectFmt("buf: \x00hello\x00\n", "buf: {s}\n", .{null_term_slice}); 2051 } 2052 2053 try expectFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"}); 2054 try expectFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); 2055 2056 { 2057 var int_slice = [_]u32{ 1, 4096, 391891, 1111111111 }; 2058 var runtime_zero: usize = 0; 2059 _ = &runtime_zero; 2060 try expectFmt("int: { 1, 4096, 391891, 1111111111 }", "int: {any}", .{int_slice[runtime_zero..]}); 2061 try expectFmt("int: { 1, 4096, 391891, 1111111111 }", "int: {d}", .{int_slice[runtime_zero..]}); 2062 try expectFmt("int: { 1, 1000, 5fad3, 423a35c7 }", "int: {x}", .{int_slice[runtime_zero..]}); 2063 try expectFmt("int: { 00001, 01000, 5fad3, 423a35c7 }", "int: {x:0>5}", .{int_slice[runtime_zero..]}); 2064 } 2065 { 2066 const S1 = struct { 2067 x: u8, 2068 }; 2069 const struct_slice: []const S1 = &[_]S1{ S1{ .x = 8 }, S1{ .x = 42 } }; 2070 try expectFmt("slice: { fmt.test.slice.S1{ .x = 8 }, fmt.test.slice.S1{ .x = 42 } }", "slice: {any}", .{struct_slice}); 2071 } 2072 { 2073 const S2 = struct { 2074 x: u8, 2075 2076 pub fn format(s: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 2077 try writer.print("S2({})", .{s.x}); 2078 } 2079 }; 2080 const struct_slice: []const S2 = &[_]S2{ S2{ .x = 8 }, S2{ .x = 42 } }; 2081 try expectFmt("slice: { S2(8), S2(42) }", "slice: {any}", .{struct_slice}); 2082 } 2083 } 2084 2085 test "escape non-printable" { 2086 try expectFmt("abc 123", "{s}", .{fmtSliceEscapeLower("abc 123")}); 2087 try expectFmt("ab\\xffc", "{s}", .{fmtSliceEscapeLower("ab\xffc")}); 2088 try expectFmt("abc 123", "{s}", .{fmtSliceEscapeUpper("abc 123")}); 2089 try expectFmt("ab\\xFFc", "{s}", .{fmtSliceEscapeUpper("ab\xffc")}); 2090 } 2091 2092 test "pointer" { 2093 { 2094 const value = @as(*align(1) i32, @ptrFromInt(0xdeadbeef)); 2095 try expectFmt("pointer: i32@deadbeef\n", "pointer: {}\n", .{value}); 2096 try expectFmt("pointer: i32@deadbeef\n", "pointer: {*}\n", .{value}); 2097 } 2098 const FnPtr = *align(1) const fn () void; 2099 { 2100 const value = @as(FnPtr, @ptrFromInt(0xdeadbeef)); 2101 try expectFmt("pointer: fn () void@deadbeef\n", "pointer: {}\n", .{value}); 2102 } 2103 { 2104 const value = @as(FnPtr, @ptrFromInt(0xdeadbeef)); 2105 try expectFmt("pointer: fn () void@deadbeef\n", "pointer: {}\n", .{value}); 2106 } 2107 } 2108 2109 test "cstr" { 2110 try expectFmt( 2111 "cstr: Test C\n", 2112 "cstr: {s}\n", 2113 .{@as([*c]const u8, @ptrCast("Test C"))}, 2114 ); 2115 try expectFmt( 2116 "cstr: Test C\n", 2117 "cstr: {s:10}\n", 2118 .{@as([*c]const u8, @ptrCast("Test C"))}, 2119 ); 2120 } 2121 2122 test "filesize" { 2123 try expectFmt("file size: 42B\n", "file size: {}\n", .{fmtIntSizeDec(42)}); 2124 try expectFmt("file size: 42B\n", "file size: {}\n", .{fmtIntSizeBin(42)}); 2125 try expectFmt("file size: 63MB\n", "file size: {}\n", .{fmtIntSizeDec(63 * 1000 * 1000)}); 2126 try expectFmt("file size: 63MiB\n", "file size: {}\n", .{fmtIntSizeBin(63 * 1024 * 1024)}); 2127 try expectFmt("file size: 42B\n", "file size: {:.2}\n", .{fmtIntSizeDec(42)}); 2128 try expectFmt("file size: 42B\n", "file size: {:>9.2}\n", .{fmtIntSizeDec(42)}); 2129 try expectFmt("file size: 66.06MB\n", "file size: {:.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); 2130 try expectFmt("file size: 60.08MiB\n", "file size: {:.2}\n", .{fmtIntSizeBin(63 * 1000 * 1000)}); 2131 try expectFmt("file size: =66.06MB=\n", "file size: {:=^9.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); 2132 try expectFmt("file size: 66.06MB\n", "file size: {: >9.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); 2133 try expectFmt("file size: 66.06MB \n", "file size: {: <9.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); 2134 try expectFmt("file size: 0.01844674407370955ZB\n", "file size: {}\n", .{fmtIntSizeDec(math.maxInt(u64))}); 2135 } 2136 2137 test "struct" { 2138 { 2139 const Struct = struct { 2140 field: u8, 2141 }; 2142 const value = Struct{ .field = 42 }; 2143 try expectFmt("struct: fmt.test.struct.Struct{ .field = 42 }\n", "struct: {}\n", .{value}); 2144 try expectFmt("struct: fmt.test.struct.Struct{ .field = 42 }\n", "struct: {}\n", .{&value}); 2145 } 2146 { 2147 const Struct = struct { 2148 a: u0, 2149 b: u1, 2150 }; 2151 const value = Struct{ .a = 0, .b = 1 }; 2152 try expectFmt("struct: fmt.test.struct.Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", .{value}); 2153 } 2154 2155 const S = struct { 2156 a: u32, 2157 b: anyerror, 2158 }; 2159 2160 const inst = S{ 2161 .a = 456, 2162 .b = error.Unused, 2163 }; 2164 2165 try expectFmt("fmt.test.struct.S{ .a = 456, .b = error.Unused }", "{}", .{inst}); 2166 // Tuples 2167 try expectFmt("{ }", "{}", .{.{}}); 2168 try expectFmt("{ -1 }", "{}", .{.{-1}}); 2169 try expectFmt("{ -1, 42, 2.5e4 }", "{}", .{.{ -1, 42, 0.25e5 }}); 2170 } 2171 2172 test "enum" { 2173 const Enum = enum { 2174 One, 2175 Two, 2176 }; 2177 const value = Enum.Two; 2178 try expectFmt("enum: fmt.test.enum.Enum.Two\n", "enum: {}\n", .{value}); 2179 try expectFmt("enum: fmt.test.enum.Enum.Two\n", "enum: {}\n", .{&value}); 2180 try expectFmt("enum: fmt.test.enum.Enum.One\n", "enum: {}\n", .{Enum.One}); 2181 try expectFmt("enum: fmt.test.enum.Enum.Two\n", "enum: {}\n", .{Enum.Two}); 2182 2183 // test very large enum to verify ct branch quota is large enough 2184 // TODO: https://github.com/ziglang/zig/issues/15609 2185 if (!((builtin.cpu.arch == .wasm32) and builtin.mode == .Debug)) { 2186 try expectFmt("enum: os.windows.win32error.Win32Error.INVALID_FUNCTION\n", "enum: {}\n", .{std.os.windows.Win32Error.INVALID_FUNCTION}); 2187 } 2188 2189 const E = enum { 2190 One, 2191 Two, 2192 Three, 2193 }; 2194 2195 const inst = E.Two; 2196 2197 try expectFmt("fmt.test.enum.E.Two", "{}", .{inst}); 2198 } 2199 2200 test "non-exhaustive enum" { 2201 const Enum = enum(u16) { 2202 One = 0x000f, 2203 Two = 0xbeef, 2204 _, 2205 }; 2206 try expectFmt("enum: fmt.test.non-exhaustive enum.Enum.One\n", "enum: {}\n", .{Enum.One}); 2207 try expectFmt("enum: fmt.test.non-exhaustive enum.Enum.Two\n", "enum: {}\n", .{Enum.Two}); 2208 try expectFmt("enum: fmt.test.non-exhaustive enum.Enum(4660)\n", "enum: {}\n", .{@as(Enum, @enumFromInt(0x1234))}); 2209 try expectFmt("enum: fmt.test.non-exhaustive enum.Enum.One\n", "enum: {x}\n", .{Enum.One}); 2210 try expectFmt("enum: fmt.test.non-exhaustive enum.Enum.Two\n", "enum: {x}\n", .{Enum.Two}); 2211 try expectFmt("enum: fmt.test.non-exhaustive enum.Enum.Two\n", "enum: {X}\n", .{Enum.Two}); 2212 try expectFmt("enum: fmt.test.non-exhaustive enum.Enum(1234)\n", "enum: {x}\n", .{@as(Enum, @enumFromInt(0x1234))}); 2213 } 2214 2215 test "float.scientific" { 2216 try expectFmt("f32: 1.34e0", "f32: {e}", .{@as(f32, 1.34)}); 2217 try expectFmt("f32: 1.234e1", "f32: {e}", .{@as(f32, 12.34)}); 2218 try expectFmt("f64: -1.234e11", "f64: {e}", .{@as(f64, -12.34e10)}); 2219 try expectFmt("f64: 9.99996e-40", "f64: {e}", .{@as(f64, 9.999960e-40)}); 2220 } 2221 2222 test "float.scientific.precision" { 2223 try expectFmt("f64: 1.40971e-42", "f64: {e:.5}", .{@as(f64, 1.409706e-42)}); 2224 try expectFmt("f64: 1.00000e-9", "f64: {e:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 814313563))))}); 2225 try expectFmt("f64: 7.81250e-3", "f64: {e:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 1006632960))))}); 2226 // libc rounds 1.000005e5 to 1.00000e5 but zig does 1.00001e5. 2227 // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. 2228 try expectFmt("f64: 1.00001e5", "f64: {e:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 1203982400))))}); 2229 } 2230 2231 test "float.special" { 2232 try expectFmt("f64: nan", "f64: {}", .{math.nan(f64)}); 2233 // negative nan is not defined by IEE 754, 2234 // and ARM thus normalizes it to positive nan 2235 if (builtin.target.cpu.arch != .arm) { 2236 try expectFmt("f64: -nan", "f64: {}", .{-math.nan(f64)}); 2237 } 2238 try expectFmt("f64: inf", "f64: {}", .{math.inf(f64)}); 2239 try expectFmt("f64: -inf", "f64: {}", .{-math.inf(f64)}); 2240 } 2241 2242 test "float.hexadecimal.special" { 2243 try expectFmt("f64: nan", "f64: {x}", .{math.nan(f64)}); 2244 // negative nan is not defined by IEE 754, 2245 // and ARM thus normalizes it to positive nan 2246 if (builtin.target.cpu.arch != .arm) { 2247 try expectFmt("f64: -nan", "f64: {x}", .{-math.nan(f64)}); 2248 } 2249 try expectFmt("f64: inf", "f64: {x}", .{math.inf(f64)}); 2250 try expectFmt("f64: -inf", "f64: {x}", .{-math.inf(f64)}); 2251 2252 try expectFmt("f64: 0x0.0p0", "f64: {x}", .{@as(f64, 0)}); 2253 try expectFmt("f64: -0x0.0p0", "f64: {x}", .{-@as(f64, 0)}); 2254 } 2255 2256 test "float.hexadecimal" { 2257 try expectFmt("f16: 0x1.554p-2", "f16: {x}", .{@as(f16, 1.0 / 3.0)}); 2258 try expectFmt("f32: 0x1.555556p-2", "f32: {x}", .{@as(f32, 1.0 / 3.0)}); 2259 try expectFmt("f64: 0x1.5555555555555p-2", "f64: {x}", .{@as(f64, 1.0 / 3.0)}); 2260 try expectFmt("f80: 0x1.5555555555555556p-2", "f80: {x}", .{@as(f80, 1.0 / 3.0)}); 2261 try expectFmt("f128: 0x1.5555555555555555555555555555p-2", "f128: {x}", .{@as(f128, 1.0 / 3.0)}); 2262 2263 try expectFmt("f16: 0x1p-14", "f16: {x}", .{math.floatMin(f16)}); 2264 try expectFmt("f32: 0x1p-126", "f32: {x}", .{math.floatMin(f32)}); 2265 try expectFmt("f64: 0x1p-1022", "f64: {x}", .{math.floatMin(f64)}); 2266 try expectFmt("f80: 0x1p-16382", "f80: {x}", .{math.floatMin(f80)}); 2267 try expectFmt("f128: 0x1p-16382", "f128: {x}", .{math.floatMin(f128)}); 2268 2269 try expectFmt("f16: 0x0.004p-14", "f16: {x}", .{math.floatTrueMin(f16)}); 2270 try expectFmt("f32: 0x0.000002p-126", "f32: {x}", .{math.floatTrueMin(f32)}); 2271 try expectFmt("f64: 0x0.0000000000001p-1022", "f64: {x}", .{math.floatTrueMin(f64)}); 2272 try expectFmt("f80: 0x0.0000000000000002p-16382", "f80: {x}", .{math.floatTrueMin(f80)}); 2273 try expectFmt("f128: 0x0.0000000000000000000000000001p-16382", "f128: {x}", .{math.floatTrueMin(f128)}); 2274 2275 try expectFmt("f16: 0x1.ffcp15", "f16: {x}", .{math.floatMax(f16)}); 2276 try expectFmt("f32: 0x1.fffffep127", "f32: {x}", .{math.floatMax(f32)}); 2277 try expectFmt("f64: 0x1.fffffffffffffp1023", "f64: {x}", .{math.floatMax(f64)}); 2278 try expectFmt("f80: 0x1.fffffffffffffffep16383", "f80: {x}", .{math.floatMax(f80)}); 2279 try expectFmt("f128: 0x1.ffffffffffffffffffffffffffffp16383", "f128: {x}", .{math.floatMax(f128)}); 2280 } 2281 2282 test "float.hexadecimal.precision" { 2283 try expectFmt("f16: 0x1.5p-2", "f16: {x:.1}", .{@as(f16, 1.0 / 3.0)}); 2284 try expectFmt("f32: 0x1.555p-2", "f32: {x:.3}", .{@as(f32, 1.0 / 3.0)}); 2285 try expectFmt("f64: 0x1.55555p-2", "f64: {x:.5}", .{@as(f64, 1.0 / 3.0)}); 2286 try expectFmt("f80: 0x1.5555555p-2", "f80: {x:.7}", .{@as(f80, 1.0 / 3.0)}); 2287 try expectFmt("f128: 0x1.555555555p-2", "f128: {x:.9}", .{@as(f128, 1.0 / 3.0)}); 2288 2289 try expectFmt("f16: 0x1.00000p0", "f16: {x:.5}", .{@as(f16, 1.0)}); 2290 try expectFmt("f32: 0x1.00000p0", "f32: {x:.5}", .{@as(f32, 1.0)}); 2291 try expectFmt("f64: 0x1.00000p0", "f64: {x:.5}", .{@as(f64, 1.0)}); 2292 try expectFmt("f80: 0x1.00000p0", "f80: {x:.5}", .{@as(f80, 1.0)}); 2293 try expectFmt("f128: 0x1.00000p0", "f128: {x:.5}", .{@as(f128, 1.0)}); 2294 } 2295 2296 test "float.decimal" { 2297 try expectFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e29)}); 2298 try expectFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)}); 2299 try expectFmt("f32: 0", "f32: {d:.0}", .{@as(f32, 0.0)}); 2300 try expectFmt("f32: 1.1", "f32: {d:.1}", .{@as(f32, 1.1234)}); 2301 try expectFmt("f32: 1234.57", "f32: {d:.2}", .{@as(f32, 1234.567)}); 2302 // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). 2303 // -11.12339... is rounded back up to -11.1234 2304 try expectFmt("f32: -11.1234", "f32: {d:.4}", .{@as(f32, -11.1234)}); 2305 try expectFmt("f32: 91.12345", "f32: {d:.5}", .{@as(f32, 91.12345)}); 2306 try expectFmt("f64: 91.1234567890", "f64: {d:.10}", .{@as(f64, 91.12345678901235)}); 2307 try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 0.0)}); 2308 try expectFmt("f64: 6", "f64: {d:.0}", .{@as(f64, 5.700)}); 2309 try expectFmt("f64: 10.0", "f64: {d:.1}", .{@as(f64, 9.999)}); 2310 try expectFmt("f64: 1.000", "f64: {d:.3}", .{@as(f64, 1.0)}); 2311 try expectFmt("f64: 0.00030000", "f64: {d:.8}", .{@as(f64, 0.0003)}); 2312 try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 1.40130e-45)}); 2313 try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 9.999960e-40)}); 2314 try expectFmt("f64: 10000000000000.00", "f64: {d:.2}", .{@as(f64, 9999999999999.999)}); 2315 try expectFmt("f64: 10000000000000000000000000000000000000", "f64: {d}", .{@as(f64, 1e37)}); 2316 try expectFmt("f64: 100000000000000000000000000000000000000", "f64: {d}", .{@as(f64, 1e38)}); 2317 } 2318 2319 test "float.libc.sanity" { 2320 try expectFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 916964781))))}); 2321 try expectFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 925353389))))}); 2322 try expectFmt("f64: 0.10000", "f64: {d:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 1036831278))))}); 2323 try expectFmt("f64: 1.00000", "f64: {d:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 1065353133))))}); 2324 try expectFmt("f64: 10.00000", "f64: {d:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 1092616192))))}); 2325 2326 // libc differences 2327 // 2328 // This is 0.015625 exactly according to gdb. We thus round down, 2329 // however glibc rounds up for some reason. This occurs for all 2330 // floats of the form x.yyyy25 on a precision point. 2331 try expectFmt("f64: 0.01563", "f64: {d:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 1015021568))))}); 2332 // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 2333 // also rounds to 630 so I'm inclined to believe libc is not 2334 // optimal here. 2335 try expectFmt("f64: 18014400656965630.00000", "f64: {d:.5}", .{@as(f64, @as(f32, @bitCast(@as(u32, 1518338049))))}); 2336 } 2337 2338 test "custom" { 2339 const Vec2 = struct { 2340 const SelfType = @This(); 2341 x: f32, 2342 y: f32, 2343 2344 pub fn format( 2345 self: SelfType, 2346 comptime fmt: []const u8, 2347 options: FormatOptions, 2348 writer: anytype, 2349 ) !void { 2350 _ = options; 2351 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { 2352 return std.fmt.format(writer, "({d:.3},{d:.3})", .{ self.x, self.y }); 2353 } else if (comptime std.mem.eql(u8, fmt, "d")) { 2354 return std.fmt.format(writer, "{d:.3}x{d:.3}", .{ self.x, self.y }); 2355 } else { 2356 @compileError("unknown format character: '" ++ fmt ++ "'"); 2357 } 2358 } 2359 }; 2360 2361 var value = Vec2{ 2362 .x = 10.2, 2363 .y = 2.22, 2364 }; 2365 try expectFmt("point: (10.200,2.220)\n", "point: {}\n", .{&value}); 2366 try expectFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{&value}); 2367 2368 // same thing but not passing a pointer 2369 try expectFmt("point: (10.200,2.220)\n", "point: {}\n", .{value}); 2370 try expectFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{value}); 2371 } 2372 2373 test "union" { 2374 const TU = union(enum) { 2375 float: f32, 2376 int: u32, 2377 }; 2378 2379 const UU = union { 2380 float: f32, 2381 int: u32, 2382 }; 2383 2384 const EU = extern union { 2385 float: f32, 2386 int: u32, 2387 }; 2388 2389 const tu_inst = TU{ .int = 123 }; 2390 const uu_inst = UU{ .int = 456 }; 2391 const eu_inst = EU{ .float = 321.123 }; 2392 2393 try expectFmt("fmt.test.union.TU{ .int = 123 }", "{}", .{tu_inst}); 2394 2395 var buf: [100]u8 = undefined; 2396 const uu_result = try bufPrint(buf[0..], "{}", .{uu_inst}); 2397 try std.testing.expectEqualStrings("fmt.test.union.UU@", uu_result[0..18]); 2398 2399 const eu_result = try bufPrint(buf[0..], "{}", .{eu_inst}); 2400 try std.testing.expectEqualStrings("fmt.test.union.EU@", eu_result[0..18]); 2401 } 2402 2403 test "struct.self-referential" { 2404 const S = struct { 2405 const SelfType = @This(); 2406 a: ?*SelfType, 2407 }; 2408 2409 var inst = S{ 2410 .a = null, 2411 }; 2412 inst.a = &inst; 2413 2414 try expectFmt("fmt.test.struct.self-referential.S{ .a = fmt.test.struct.self-referential.S{ .a = fmt.test.struct.self-referential.S{ .a = fmt.test.struct.self-referential.S{ ... } } } }", "{}", .{inst}); 2415 } 2416 2417 test "struct.zero-size" { 2418 const A = struct { 2419 fn foo() void {} 2420 }; 2421 const B = struct { 2422 a: A, 2423 c: i32, 2424 }; 2425 2426 const a = A{}; 2427 const b = B{ .a = a, .c = 0 }; 2428 2429 try expectFmt("fmt.test.struct.zero-size.B{ .a = fmt.test.struct.zero-size.A{ }, .c = 0 }", "{}", .{b}); 2430 } 2431 2432 test "bytes.hex" { 2433 const some_bytes = "\xCA\xFE\xBA\xBE"; 2434 try expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{fmtSliceHexLower(some_bytes)}); 2435 try expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{fmtSliceHexUpper(some_bytes)}); 2436 //Test Slices 2437 try expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{fmtSliceHexUpper(some_bytes[0..2])}); 2438 try expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{fmtSliceHexLower(some_bytes[2..])}); 2439 const bytes_with_zeros = "\x00\x0E\xBA\xBE"; 2440 try expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{fmtSliceHexLower(bytes_with_zeros)}); 2441 } 2442 2443 /// Encodes a sequence of bytes as hexadecimal digits. 2444 /// Returns an array containing the encoded bytes. 2445 pub fn bytesToHex(input: anytype, case: Case) [input.len * 2]u8 { 2446 if (input.len == 0) return [_]u8{}; 2447 comptime assert(@TypeOf(input[0]) == u8); // elements to encode must be unsigned bytes 2448 2449 const charset = "0123456789" ++ if (case == .upper) "ABCDEF" else "abcdef"; 2450 var result: [input.len * 2]u8 = undefined; 2451 for (input, 0..) |b, i| { 2452 result[i * 2 + 0] = charset[b >> 4]; 2453 result[i * 2 + 1] = charset[b & 15]; 2454 } 2455 return result; 2456 } 2457 2458 /// Decodes the sequence of bytes represented by the specified string of 2459 /// hexadecimal characters. 2460 /// Returns a slice of the output buffer containing the decoded bytes. 2461 pub fn hexToBytes(out: []u8, input: []const u8) ![]u8 { 2462 // Expect 0 or n pairs of hexadecimal digits. 2463 if (input.len & 1 != 0) 2464 return error.InvalidLength; 2465 if (out.len * 2 < input.len) 2466 return error.NoSpaceLeft; 2467 2468 var in_i: usize = 0; 2469 while (in_i < input.len) : (in_i += 2) { 2470 const hi = try charToDigit(input[in_i], 16); 2471 const lo = try charToDigit(input[in_i + 1], 16); 2472 out[in_i / 2] = (hi << 4) | lo; 2473 } 2474 2475 return out[0 .. in_i / 2]; 2476 } 2477 2478 test bytesToHex { 2479 const input = "input slice"; 2480 const encoded = bytesToHex(input, .lower); 2481 var decoded: [input.len]u8 = undefined; 2482 try std.testing.expectEqualSlices(u8, input, try hexToBytes(&decoded, &encoded)); 2483 } 2484 2485 test hexToBytes { 2486 var buf: [32]u8 = undefined; 2487 try expectFmt("90" ** 32, "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, "90" ** 32))}); 2488 try expectFmt("ABCD", "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, "ABCD"))}); 2489 try expectFmt("", "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, ""))}); 2490 try std.testing.expectError(error.InvalidCharacter, hexToBytes(&buf, "012Z")); 2491 try std.testing.expectError(error.InvalidLength, hexToBytes(&buf, "AAA")); 2492 try std.testing.expectError(error.NoSpaceLeft, hexToBytes(buf[0..1], "ABAB")); 2493 } 2494 2495 test "formatIntValue with comptime_int" { 2496 const value: comptime_int = 123456789123456789; 2497 2498 var buf: [20]u8 = undefined; 2499 var fbs = std.io.fixedBufferStream(&buf); 2500 try formatIntValue(value, "", FormatOptions{}, fbs.writer()); 2501 try std.testing.expectEqualStrings("123456789123456789", fbs.getWritten()); 2502 } 2503 2504 test "formatFloatValue with comptime_float" { 2505 const value: comptime_float = 1.0; 2506 2507 var buf: [20]u8 = undefined; 2508 var fbs = std.io.fixedBufferStream(&buf); 2509 try formatFloatValue(value, "", FormatOptions{}, fbs.writer()); 2510 try std.testing.expectEqualStrings(fbs.getWritten(), "1e0"); 2511 2512 try expectFmt("1e0", "{}", .{value}); 2513 try expectFmt("1e0", "{}", .{1.0}); 2514 } 2515 2516 test "formatType max_depth" { 2517 const Vec2 = struct { 2518 const SelfType = @This(); 2519 x: f32, 2520 y: f32, 2521 2522 pub fn format( 2523 self: SelfType, 2524 comptime fmt: []const u8, 2525 options: FormatOptions, 2526 writer: anytype, 2527 ) !void { 2528 _ = options; 2529 if (fmt.len == 0) { 2530 return std.fmt.format(writer, "({d:.3},{d:.3})", .{ self.x, self.y }); 2531 } else { 2532 @compileError("unknown format string: '" ++ fmt ++ "'"); 2533 } 2534 } 2535 }; 2536 const E = enum { 2537 One, 2538 Two, 2539 Three, 2540 }; 2541 const TU = union(enum) { 2542 const SelfType = @This(); 2543 float: f32, 2544 int: u32, 2545 ptr: ?*SelfType, 2546 }; 2547 const S = struct { 2548 const SelfType = @This(); 2549 a: ?*SelfType, 2550 tu: TU, 2551 e: E, 2552 vec: Vec2, 2553 }; 2554 2555 var inst = S{ 2556 .a = null, 2557 .tu = TU{ .ptr = null }, 2558 .e = E.Two, 2559 .vec = Vec2{ .x = 10.2, .y = 2.22 }, 2560 }; 2561 inst.a = &inst; 2562 inst.tu.ptr = &inst.tu; 2563 2564 var buf: [1000]u8 = undefined; 2565 var fbs = std.io.fixedBufferStream(&buf); 2566 try formatType(inst, "", FormatOptions{}, fbs.writer(), 0); 2567 try std.testing.expectEqualStrings("fmt.test.formatType max_depth.S{ ... }", fbs.getWritten()); 2568 2569 fbs.reset(); 2570 try formatType(inst, "", FormatOptions{}, fbs.writer(), 1); 2571 try std.testing.expectEqualStrings("fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }", fbs.getWritten()); 2572 2573 fbs.reset(); 2574 try formatType(inst, "", FormatOptions{}, fbs.writer(), 2); 2575 try std.testing.expectEqualStrings("fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }", fbs.getWritten()); 2576 2577 fbs.reset(); 2578 try formatType(inst, "", FormatOptions{}, fbs.writer(), 3); 2579 try std.testing.expectEqualStrings("fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }", fbs.getWritten()); 2580 } 2581 2582 test "positional" { 2583 try expectFmt("2 1 0", "{2} {1} {0}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); 2584 try expectFmt("2 1 0", "{2} {1} {}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); 2585 try expectFmt("0 0", "{0} {0}", .{@as(usize, 0)}); 2586 try expectFmt("0 1", "{} {1}", .{ @as(usize, 0), @as(usize, 1) }); 2587 try expectFmt("1 0 0 1", "{1} {} {0} {}", .{ @as(usize, 0), @as(usize, 1) }); 2588 } 2589 2590 test "positional with specifier" { 2591 try expectFmt("10.0", "{0d:.1}", .{@as(f64, 9.999)}); 2592 } 2593 2594 test "positional/alignment/width/precision" { 2595 try expectFmt("10.0", "{0d: >3.1}", .{@as(f64, 9.999)}); 2596 } 2597 2598 test "vector" { 2599 if (builtin.cpu.arch == .armeb and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22060 2600 if (builtin.target.cpu.arch == .riscv64) { 2601 // https://github.com/ziglang/zig/issues/4486 2602 return error.SkipZigTest; 2603 } 2604 2605 const vbool: @Vector(4, bool) = [_]bool{ true, false, true, false }; 2606 const vi64: @Vector(4, i64) = [_]i64{ -2, -1, 0, 1 }; 2607 const vu64: @Vector(4, u64) = [_]u64{ 1000, 2000, 3000, 4000 }; 2608 2609 try expectFmt("{ true, false, true, false }", "{}", .{vbool}); 2610 try expectFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); 2611 try expectFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64}); 2612 try expectFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); 2613 try expectFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); 2614 2615 const x: [4]u64 = undefined; 2616 const vp: @Vector(4, *const u64) = [_]*const u64{ &x[0], &x[1], &x[2], &x[3] }; 2617 const vop: @Vector(4, ?*const u64) = [_]?*const u64{ &x[0], null, null, &x[3] }; 2618 2619 var expect_buffer: [@sizeOf(usize) * 2 * 4 + 64]u8 = undefined; 2620 try expectFmt(try bufPrint( 2621 &expect_buffer, 2622 "{{ {}, {}, {}, {} }}", 2623 .{ &x[0], &x[1], &x[2], &x[3] }, 2624 ), "{}", .{vp}); 2625 try expectFmt(try bufPrint( 2626 &expect_buffer, 2627 "{{ {?}, null, null, {?} }}", 2628 .{ &x[0], &x[3] }, 2629 ), "{any}", .{vop}); 2630 } 2631 2632 test "enum-literal" { 2633 try expectFmt(".hello_world", "{}", .{.hello_world}); 2634 } 2635 2636 test "padding" { 2637 try expectFmt("Simple", "{s}", .{"Simple"}); 2638 try expectFmt(" true", "{:10}", .{true}); 2639 try expectFmt(" true", "{:>10}", .{true}); 2640 try expectFmt("======true", "{:=>10}", .{true}); 2641 try expectFmt("true======", "{:=<10}", .{true}); 2642 try expectFmt(" true ", "{:^10}", .{true}); 2643 try expectFmt("===true===", "{:=^10}", .{true}); 2644 try expectFmt(" Minimum width", "{s:18} width", .{"Minimum"}); 2645 try expectFmt("==================Filled", "{s:=>24}", .{"Filled"}); 2646 try expectFmt(" Centered ", "{s:^24}", .{"Centered"}); 2647 try expectFmt("-", "{s:-^1}", .{""}); 2648 try expectFmt("==crêpe===", "{s:=^10}", .{"crêpe"}); 2649 try expectFmt("=====crêpe", "{s:=>10}", .{"crêpe"}); 2650 try expectFmt("crêpe=====", "{s:=<10}", .{"crêpe"}); 2651 try expectFmt("====a", "{c:=>5}", .{'a'}); 2652 try expectFmt("==a==", "{c:=^5}", .{'a'}); 2653 try expectFmt("a====", "{c:=<5}", .{'a'}); 2654 } 2655 2656 test "padding fill char utf" { 2657 try expectFmt("──crêpe───", "{s:─^10}", .{"crêpe"}); 2658 try expectFmt("─────crêpe", "{s:─>10}", .{"crêpe"}); 2659 try expectFmt("crêpe─────", "{s:─<10}", .{"crêpe"}); 2660 try expectFmt("────a", "{c:─>5}", .{'a'}); 2661 try expectFmt("──a──", "{c:─^5}", .{'a'}); 2662 try expectFmt("a────", "{c:─<5}", .{'a'}); 2663 } 2664 2665 test "decimal float padding" { 2666 const number: f32 = 3.1415; 2667 try expectFmt("left-pad: **3.142\n", "left-pad: {d:*>7.3}\n", .{number}); 2668 try expectFmt("center-pad: *3.142*\n", "center-pad: {d:*^7.3}\n", .{number}); 2669 try expectFmt("right-pad: 3.142**\n", "right-pad: {d:*<7.3}\n", .{number}); 2670 } 2671 2672 test "sci float padding" { 2673 const number: f32 = 3.1415; 2674 try expectFmt("left-pad: ****3.142e0\n", "left-pad: {e:*>11.3}\n", .{number}); 2675 try expectFmt("center-pad: **3.142e0**\n", "center-pad: {e:*^11.3}\n", .{number}); 2676 try expectFmt("right-pad: 3.142e0****\n", "right-pad: {e:*<11.3}\n", .{number}); 2677 } 2678 2679 test "padding.zero" { 2680 try expectFmt("zero-pad: '0042'", "zero-pad: '{:04}'", .{42}); 2681 try expectFmt("std-pad: ' 42'", "std-pad: '{:10}'", .{42}); 2682 try expectFmt("std-pad-1: '001'", "std-pad-1: '{:0>3}'", .{1}); 2683 try expectFmt("std-pad-2: '911'", "std-pad-2: '{:1<03}'", .{9}); 2684 try expectFmt("std-pad-3: ' 1'", "std-pad-3: '{:>03}'", .{1}); 2685 try expectFmt("center-pad: '515'", "center-pad: '{:5^03}'", .{1}); 2686 } 2687 2688 test "null" { 2689 const inst = null; 2690 try expectFmt("null", "{}", .{inst}); 2691 } 2692 2693 test "type" { 2694 try expectFmt("u8", "{}", .{u8}); 2695 try expectFmt("?f32", "{}", .{?f32}); 2696 try expectFmt("[]const u8", "{}", .{[]const u8}); 2697 } 2698 2699 test "named arguments" { 2700 try expectFmt("hello world!", "{s} world{c}", .{ "hello", '!' }); 2701 try expectFmt("hello world!", "{[greeting]s} world{[punctuation]c}", .{ .punctuation = '!', .greeting = "hello" }); 2702 try expectFmt("hello world!", "{[1]s} world{[0]c}", .{ '!', "hello" }); 2703 } 2704 2705 test "runtime width specifier" { 2706 const width: usize = 9; 2707 try expectFmt("~~hello~~", "{s:~^[1]}", .{ "hello", width }); 2708 try expectFmt("~~hello~~", "{s:~^[width]}", .{ .string = "hello", .width = width }); 2709 try expectFmt(" hello", "{s:[1]}", .{ "hello", width }); 2710 try expectFmt("42 hello", "{d} {s:[2]}", .{ 42, "hello", width }); 2711 } 2712 2713 test "runtime precision specifier" { 2714 const number: f32 = 3.1415; 2715 const precision: usize = 2; 2716 try expectFmt("3.14e0", "{:1.[1]}", .{ number, precision }); 2717 try expectFmt("3.14e0", "{:1.[precision]}", .{ .number = number, .precision = precision }); 2718 } 2719 2720 test "recursive format function" { 2721 const R = union(enum) { 2722 const R = @This(); 2723 Leaf: i32, 2724 Branch: struct { left: *const R, right: *const R }, 2725 2726 pub fn format(self: R, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 2727 return switch (self) { 2728 .Leaf => |n| std.fmt.format(writer, "Leaf({})", .{n}), 2729 .Branch => |b| std.fmt.format(writer, "Branch({}, {})", .{ b.left, b.right }), 2730 }; 2731 } 2732 }; 2733 2734 var r = R{ .Leaf = 1 }; 2735 try expectFmt("Leaf(1)\n", "{}\n", .{&r}); 2736 } 2737 2738 pub const hex_charset = "0123456789abcdef"; 2739 2740 /// Converts an unsigned integer of any multiple of u8 to an array of lowercase 2741 /// hex bytes, little endian. 2742 pub fn hex(x: anytype) [@sizeOf(@TypeOf(x)) * 2]u8 { 2743 comptime assert(@typeInfo(@TypeOf(x)).int.signedness == .unsigned); 2744 var result: [@sizeOf(@TypeOf(x)) * 2]u8 = undefined; 2745 var i: usize = 0; 2746 while (i < result.len / 2) : (i += 1) { 2747 const byte: u8 = @truncate(x >> @intCast(8 * i)); 2748 result[i * 2 + 0] = hex_charset[byte >> 4]; 2749 result[i * 2 + 1] = hex_charset[byte & 15]; 2750 } 2751 return result; 2752 } 2753 2754 test hex { 2755 { 2756 const x = hex(@as(u32, 0xdeadbeef)); 2757 try std.testing.expect(x.len == 8); 2758 try std.testing.expectEqualStrings("efbeadde", &x); 2759 } 2760 { 2761 const s = "[" ++ hex(@as(u64, 0x12345678_abcdef00)) ++ "]"; 2762 try std.testing.expect(s.len == 18); 2763 try std.testing.expectEqualStrings("[00efcdab78563412]", s); 2764 } 2765 }