| ... | @@ -69,6 +69,7 @@ pub const FormatOptions = struct { | ... | @@ -69,6 +69,7 @@ pub const FormatOptions = struct { |
| 69 | /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. | 69 | /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. |
| 70 | /// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max. | 70 | /// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max. |
| 71 | /// - `*`: output the address of the value instead of the value itself. | 71 | /// - `*`: output the address of the value instead of the value itself. |
| | 72 | /// - `any`: output a value of any type using its default format |
| 72 | /// | 73 | /// |
| 73 | /// If a formatted user type contains a function of the type | 74 | /// If a formatted user type contains a function of the type |
| 74 | /// ``` | 75 | /// ``` |
| ... | @@ -387,17 +388,32 @@ pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @T | ... | @@ -387,17 +388,32 @@ pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @T |
| 387 | return; | 388 | return; |
| 388 | } | 389 | } |
| 389 | }, | 390 | }, |
| 390 | .Array => |info| { | | |
| 391 | try writer.writeAll(@typeName(info.child) ++ "@"); | | |
| 392 | try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); | | |
| 393 | return; | | |
| 394 | }, | | |
| 395 | else => {}, | 391 | else => {}, |
| 396 | } | 392 | } |
| 397 | | 393 | |
| 398 | @compileError("Cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); | 394 | @compileError("Cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); |
| 399 | } | 395 | } |
| 400 | | 396 | |
| | 397 | // This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 |
| | 398 | const ANY = "any"; |
| | 399 | |
| | 400 | fn defaultSpec(comptime T: type) [:0]const u8 { |
| | 401 | switch (@typeInfo(T)) { |
| | 402 | .Array => |_| return ANY, |
| | 403 | .Pointer => |ptr_info| switch (ptr_info.size) { |
| | 404 | .One => switch (@typeInfo(ptr_info.child)) { |
| | 405 | .Array => |_| return "*", |
| | 406 | else => {}, |
| | 407 | }, |
| | 408 | .Many, .C => return "*", |
| | 409 | .Slice => return ANY, |
| | 410 | }, |
| | 411 | .Optional => |info| return defaultSpec(info.child), |
| | 412 | else => {}, |
| | 413 | } |
| | 414 | return ""; |
| | 415 | } |
| | 416 | |
| 401 | pub fn formatType( | 417 | pub fn formatType( |
| 402 | value: anytype, | 418 | value: anytype, |
| 403 | comptime fmt: []const u8, | 419 | comptime fmt: []const u8, |
| ... | @@ -405,18 +421,19 @@ pub fn formatType( | ... | @@ -405,18 +421,19 @@ pub fn formatType( |
| 405 | writer: anytype, | 421 | writer: anytype, |
| 406 | max_depth: usize, | 422 | max_depth: usize, |
| 407 | ) @TypeOf(writer).Error!void { | 423 | ) @TypeOf(writer).Error!void { |
| 408 | if (comptime std.mem.eql(u8, fmt, "*")) { | 424 | const actual_fmt = comptime if (std.mem.eql(u8, fmt, ANY)) defaultSpec(@TypeOf(value)) else fmt; |
| | 425 | if (comptime std.mem.eql(u8, actual_fmt, "*")) { |
| 409 | return formatAddress(value, options, writer); | 426 | return formatAddress(value, options, writer); |
| 410 | } | 427 | } |
| 411 | | 428 | |
| 412 | const T = @TypeOf(value); | 429 | const T = @TypeOf(value); |
| 413 | if (comptime std.meta.trait.hasFn("format")(T)) { | 430 | if (comptime std.meta.trait.hasFn("format")(T)) { |
| 414 | return try value.format(fmt, options, writer); | 431 | return try value.format(actual_fmt, options, writer); |
| 415 | } | 432 | } |
| 416 | | 433 | |
| 417 | switch (@typeInfo(T)) { | 434 | switch (@typeInfo(T)) { |
| 418 | .ComptimeInt, .Int, .ComptimeFloat, .Float => { | 435 | .ComptimeInt, .Int, .ComptimeFloat, .Float => { |
| 419 | return formatValue(value, fmt, options, writer); | 436 | return formatValue(value, actual_fmt, options, writer); |
| 420 | }, | 437 | }, |
| 421 | .Void => { | 438 | .Void => { |
| 422 | return formatBuf("void", options, writer); | 439 | return formatBuf("void", options, writer); |
| ... | @@ -426,16 +443,16 @@ pub fn formatType( | ... | @@ -426,16 +443,16 @@ pub fn formatType( |
| 426 | }, | 443 | }, |
| 427 | .Optional => { | 444 | .Optional => { |
| 428 | if (value) |payload| { | 445 | if (value) |payload| { |
| 429 | return formatType(payload, fmt, options, writer, max_depth); | 446 | return formatType(payload, actual_fmt, options, writer, max_depth); |
| 430 | } else { | 447 | } else { |
| 431 | return formatBuf("null", options, writer); | 448 | return formatBuf("null", options, writer); |
| 432 | } | 449 | } |
| 433 | }, | 450 | }, |
| 434 | .ErrorUnion => { | 451 | .ErrorUnion => { |
| 435 | if (value) |payload| { | 452 | if (value) |payload| { |
| 436 | return formatType(payload, fmt, options, writer, max_depth); | 453 | return formatType(payload, actual_fmt, options, writer, max_depth); |
| 437 | } else |err| { | 454 | } else |err| { |
| 438 | return formatType(err, fmt, options, writer, max_depth); | 455 | return formatType(err, actual_fmt, options, writer, max_depth); |
| 439 | } | 456 | } |
| 440 | }, | 457 | }, |
| 441 | .ErrorSet => { | 458 | .ErrorSet => { |
| ... | @@ -461,7 +478,7 @@ pub fn formatType( | ... | @@ -461,7 +478,7 @@ pub fn formatType( |
| 461 | } | 478 | } |
| 462 | | 479 | |
| 463 | try writer.writeAll("("); | 480 | try writer.writeAll("("); |
| 464 | try formatType(@enumToInt(value), fmt, options, writer, max_depth); | 481 | try formatType(@enumToInt(value), actual_fmt, options, writer, max_depth); |
| 465 | try writer.writeAll(")"); | 482 | try writer.writeAll(")"); |
| 466 | }, | 483 | }, |
| 467 | .Union => |info| { | 484 | .Union => |info| { |
| ... | @@ -475,7 +492,7 @@ pub fn formatType( | ... | @@ -475,7 +492,7 @@ pub fn formatType( |
| 475 | try writer.writeAll(" = "); | 492 | try writer.writeAll(" = "); |
| 476 | inline for (info.fields) |u_field| { | 493 | inline for (info.fields) |u_field| { |
| 477 | if (value == @field(UnionTagType, u_field.name)) { | 494 | if (value == @field(UnionTagType, u_field.name)) { |
| 478 | try formatType(@field(value, u_field.name), fmt, options, writer, max_depth - 1); | 495 | try formatType(@field(value, u_field.name), ANY, options, writer, max_depth - 1); |
| 479 | } | 496 | } |
| 480 | } | 497 | } |
| 481 | try writer.writeAll(" }"); | 498 | try writer.writeAll(" }"); |
| ... | @@ -497,48 +514,54 @@ pub fn formatType( | ... | @@ -497,48 +514,54 @@ pub fn formatType( |
| 497 | } | 514 | } |
| 498 | try writer.writeAll(f.name); | 515 | try writer.writeAll(f.name); |
| 499 | try writer.writeAll(" = "); | 516 | try writer.writeAll(" = "); |
| 500 | try formatType(@field(value, f.name), fmt, options, writer, max_depth - 1); | 517 | try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1); |
| 501 | } | 518 | } |
| 502 | try writer.writeAll(" }"); | 519 | try writer.writeAll(" }"); |
| 503 | }, | 520 | }, |
| 504 | .Pointer => |ptr_info| switch (ptr_info.size) { | 521 | .Pointer => |ptr_info| switch (ptr_info.size) { |
| 505 | .One => switch (@typeInfo(ptr_info.child)) { | 522 | .One => switch (@typeInfo(ptr_info.child)) { |
| 506 | .Array => |info| { | 523 | .Array => |info| { |
| | 524 | if (actual_fmt.len == 0) |
| | 525 | @compileError("cannot format array ref without a specifier (i.e. {s} or {*})"); |
| 507 | if (info.child == u8) { | 526 | if (info.child == u8) { |
| 508 | if (fmt.len > 0 and comptime mem.indexOfScalar(u8, "sxXeEzZ", fmt[0]) != null) { | 527 | if (comptime mem.indexOfScalar(u8, "sxXeEzZ", actual_fmt[0]) != null) { |
| 509 | return formatText(value, fmt, options, writer); | 528 | return formatText(value, actual_fmt, options, writer); |
| 510 | } | 529 | } |
| 511 | } | 530 | } |
| 512 | return format(writer, "{s}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value) }); | 531 | @compileError("Unknown format string: '" ++ actual_fmt ++ "'"); |
| 513 | }, | 532 | }, |
| 514 | .Enum, .Union, .Struct => { | 533 | .Enum, .Union, .Struct => { |
| 515 | return formatType(value.*, fmt, options, writer, max_depth); | 534 | return formatType(value.*, actual_fmt, options, writer, max_depth); |
| 516 | }, | 535 | }, |
| 517 | else => return format(writer, "{s}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value) }), | 536 | else => return format(writer, "{s}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value) }), |
| 518 | }, | 537 | }, |
| 519 | .Many, .C => { | 538 | .Many, .C => { |
| | 539 | if (actual_fmt.len == 0) |
| | 540 | @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); |
| 520 | if (ptr_info.sentinel) |sentinel| { | 541 | if (ptr_info.sentinel) |sentinel| { |
| 521 | return formatType(mem.span(value), fmt, options, writer, max_depth); | 542 | return formatType(mem.span(value), actual_fmt, options, writer, max_depth); |
| 522 | } | 543 | } |
| 523 | if (ptr_info.child == u8) { | 544 | if (ptr_info.child == u8) { |
| 524 | if (fmt.len > 0 and comptime mem.indexOfScalar(u8, "sxXeEzZ", fmt[0]) != null) { | 545 | if (comptime mem.indexOfScalar(u8, "sxXeEzZ", actual_fmt[0]) != null) { |
| 525 | return formatText(mem.span(value), fmt, options, writer); | 546 | return formatText(mem.span(value), actual_fmt, options, writer); |
| 526 | } | 547 | } |
| 527 | } | 548 | } |
| 528 | return format(writer, "{s}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value) }); | 549 | @compileError("Unknown format string: '" ++ actual_fmt ++ "'"); |
| 529 | }, | 550 | }, |
| 530 | .Slice => { | 551 | .Slice => { |
| | 552 | if (actual_fmt.len == 0) |
| | 553 | @compileError("cannot format slice without a specifier (i.e. {s} or {any})"); |
| 531 | if (max_depth == 0) { | 554 | if (max_depth == 0) { |
| 532 | return writer.writeAll("{ ... }"); | 555 | return writer.writeAll("{ ... }"); |
| 533 | } | 556 | } |
| 534 | if (ptr_info.child == u8) { | 557 | if (ptr_info.child == u8) { |
| 535 | if (fmt.len > 0 and comptime mem.indexOfScalar(u8, "sxXeEzZ", fmt[0]) != null) { | 558 | if (comptime mem.indexOfScalar(u8, "sxXeEzZ", actual_fmt[0]) != null) { |
| 536 | return formatText(value, fmt, options, writer); | 559 | return formatText(value, actual_fmt, options, writer); |
| 537 | } | 560 | } |
| 538 | } | 561 | } |
| 539 | try writer.writeAll("{ "); | 562 | try writer.writeAll("{ "); |
| 540 | for (value) |elem, i| { | 563 | for (value) |elem, i| { |
| 541 | try formatType(elem, fmt, options, writer, max_depth - 1); | 564 | try formatType(elem, actual_fmt, options, writer, max_depth - 1); |
| 542 | if (i != value.len - 1) { | 565 | if (i != value.len - 1) { |
| 543 | try writer.writeAll(", "); | 566 | try writer.writeAll(", "); |
| 544 | } | 567 | } |
| ... | @@ -547,17 +570,19 @@ pub fn formatType( | ... | @@ -547,17 +570,19 @@ pub fn formatType( |
| 547 | }, | 570 | }, |
| 548 | }, | 571 | }, |
| 549 | .Array => |info| { | 572 | .Array => |info| { |
| | 573 | if (actual_fmt.len == 0) |
| | 574 | @compileError("cannot format array without a specifier (i.e. {s} or {any})"); |
| 550 | if (max_depth == 0) { | 575 | if (max_depth == 0) { |
| 551 | return writer.writeAll("{ ... }"); | 576 | return writer.writeAll("{ ... }"); |
| 552 | } | 577 | } |
| 553 | if (info.child == u8) { | 578 | if (info.child == u8) { |
| 554 | if (fmt.len > 0 and comptime mem.indexOfScalar(u8, "sxXeEzZ", fmt[0]) != null) { | 579 | if (comptime mem.indexOfScalar(u8, "sxXeEzZ", actual_fmt[0]) != null) { |
| 555 | return formatText(&value, fmt, options, writer); | 580 | return formatText(&value, actual_fmt, options, writer); |
| 556 | } | 581 | } |
| 557 | } | 582 | } |
| 558 | try writer.writeAll("{ "); | 583 | try writer.writeAll("{ "); |
| 559 | for (value) |elem, i| { | 584 | for (value) |elem, i| { |
| 560 | try formatType(elem, fmt, options, writer, max_depth - 1); | 585 | try formatType(elem, actual_fmt, options, writer, max_depth - 1); |
| 561 | if (i < value.len - 1) { | 586 | if (i < value.len - 1) { |
| 562 | try writer.writeAll(", "); | 587 | try writer.writeAll(", "); |
| 563 | } | 588 | } |
| ... | @@ -568,7 +593,7 @@ pub fn formatType( | ... | @@ -568,7 +593,7 @@ pub fn formatType( |
| 568 | try writer.writeAll("{ "); | 593 | try writer.writeAll("{ "); |
| 569 | var i: usize = 0; | 594 | var i: usize = 0; |
| 570 | while (i < info.len) : (i += 1) { | 595 | while (i < info.len) : (i += 1) { |
| 571 | try formatValue(value[i], fmt, options, writer); | 596 | try formatValue(value[i], actual_fmt, options, writer); |
| 572 | if (i < info.len - 1) { | 597 | if (i < info.len - 1) { |
| 573 | try writer.writeAll(", "); | 598 | try writer.writeAll(", "); |
| 574 | } | 599 | } |
| ... | @@ -1668,7 +1693,7 @@ test "slice" { | ... | @@ -1668,7 +1693,7 @@ test "slice" { |
| 1668 | { | 1693 | { |
| 1669 | var int_slice = [_]u32{ 1, 4096, 391891, 1111111111 }; | 1694 | var int_slice = [_]u32{ 1, 4096, 391891, 1111111111 }; |
| 1670 | var runtime_zero: usize = 0; | 1695 | var runtime_zero: usize = 0; |
| 1671 | try expectFmt("int: { 1, 4096, 391891, 1111111111 }", "int: {}", .{int_slice[runtime_zero..]}); | 1696 | try expectFmt("int: { 1, 4096, 391891, 1111111111 }", "int: {any}", .{int_slice[runtime_zero..]}); |
| 1672 | try expectFmt("int: { 1, 4096, 391891, 1111111111 }", "int: {d}", .{int_slice[runtime_zero..]}); | 1697 | try expectFmt("int: { 1, 4096, 391891, 1111111111 }", "int: {d}", .{int_slice[runtime_zero..]}); |
| 1673 | try expectFmt("int: { 1, 1000, 5fad3, 423a35c7 }", "int: {x}", .{int_slice[runtime_zero..]}); | 1698 | try expectFmt("int: { 1, 1000, 5fad3, 423a35c7 }", "int: {x}", .{int_slice[runtime_zero..]}); |
| 1674 | try expectFmt("int: { 00001, 01000, 5fad3, 423a35c7 }", "int: {x:0>5}", .{int_slice[runtime_zero..]}); | 1699 | try expectFmt("int: { 00001, 01000, 5fad3, 423a35c7 }", "int: {x:0>5}", .{int_slice[runtime_zero..]}); |