diff options
Diffstat (limited to 'std/fmt.zig')
| -rw-r--r-- | std/fmt.zig | 451 |
1 files changed, 254 insertions, 197 deletions
diff --git a/std/fmt.zig b/std/fmt.zig index 75e0ce85cb..7bf1fa3d4d 100644 --- a/std/fmt.zig +++ b/std/fmt.zig @@ -13,7 +13,13 @@ pub const default_max_depth = 3; /// Renders fmt string with args, calling output with slices of bytes. /// If `output` returns an error, the error is returned from `format` and /// `output` is not called again. -pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, comptime fmt: []const u8, args: ...) Errors!void { +pub fn format( + context: var, + comptime Errors: type, + output: fn (@typeOf(context), []const u8) Errors!void, + comptime fmt: []const u8, + args: ..., +) Errors!void { const State = enum { Start, OpenBrace, @@ -28,7 +34,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), inline for (fmt) |c, i| { switch (state) { - State.Start => switch (c) { + .Start => switch (c) { '{' => { if (start_index < i) { try output(context, fmt[start_index..i]); @@ -45,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), }, else => {}, }, - State.OpenBrace => switch (c) { + .OpenBrace => switch (c) { '{' => { state = State.Start; start_index = i; @@ -61,14 +67,14 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), state = State.FormatString; }, }, - State.CloseBrace => switch (c) { + .CloseBrace => switch (c) { '}' => { state = State.Start; start_index = i; }, else => @compileError("Single '}' encountered in format string"), }, - State.FormatString => switch (c) { + .FormatString => switch (c) { '}' => { const s = start_index + 1; try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth); @@ -78,7 +84,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), }, else => {}, }, - State.Pointer => switch (c) { + .Pointer => switch (c) { '}' => { try output(context, @typeName(@typeOf(args[next_arg]).Child)); try output(context, "@"); @@ -114,88 +120,93 @@ pub fn formatType( ) Errors!void { const T = @typeOf(value); switch (@typeInfo(T)) { - builtin.TypeId.ComptimeInt, builtin.TypeId.Int, builtin.TypeId.Float => { + .ComptimeInt, .Int, .Float => { return formatValue(value, fmt, context, Errors, output); }, - builtin.TypeId.Void => { + .Void => { return output(context, "void"); }, - builtin.TypeId.Bool => { + .Bool => { return output(context, if (value) "true" else "false"); }, - builtin.TypeId.Optional => { + .Optional => { if (value) |payload| { return formatType(payload, fmt, context, Errors, output, max_depth); } else { return output(context, "null"); } }, - builtin.TypeId.ErrorUnion => { + .ErrorUnion => { if (value) |payload| { return formatType(payload, fmt, context, Errors, output, max_depth); } else |err| { return formatType(err, fmt, context, Errors, output, max_depth); } }, - builtin.TypeId.ErrorSet => { + .ErrorSet => { try output(context, "error."); return output(context, @errorName(value)); }, - builtin.TypeId.Promise => { + .Promise => { return format(context, Errors, output, "promise@{x}", @ptrToInt(value)); }, - builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { - if (comptime std.meta.trait.hasFn("format")(T)) return value.format(fmt, context, Errors, output); + .Enum => { + if (comptime std.meta.trait.hasFn("format")(T)) { + return value.format(fmt, context, Errors, output); + } try output(context, @typeName(T)); - switch (comptime @typeId(T)) { - builtin.TypeId.Enum => { - try output(context, "."); - try formatType(@tagName(value), "", context, Errors, output, max_depth); - return; - }, - builtin.TypeId.Struct => { - if (max_depth == 0) { - return output(context, "{ ... }"); - } - comptime var field_i = 0; - inline while (field_i < @memberCount(T)) : (field_i += 1) { - if (field_i == 0) { - try output(context, "{ ."); - } else { - try output(context, ", ."); - } - try output(context, @memberName(T, field_i)); - try output(context, " = "); - try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth - 1); - } - try output(context, " }"); - }, - builtin.TypeId.Union => { - if (max_depth == 0) { - return output(context, "{ ... }"); - } - const info = @typeInfo(T).Union; - if (info.tag_type) |UnionTagType| { - try output(context, "{ ."); - try output(context, @tagName(UnionTagType(value))); - try output(context, " = "); - inline for (info.fields) |u_field| { - if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { - try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth - 1); - } - } - try output(context, " }"); - } else { - try format(context, Errors, output, "@{x}", @ptrToInt(&value)); + try output(context, "."); + return formatType(@tagName(value), "", context, Errors, output, max_depth); + }, + .Union => { + if (comptime std.meta.trait.hasFn("format")(T)) { + return value.format(fmt, context, Errors, output); + } + + try output(context, @typeName(T)); + if (max_depth == 0) { + return output(context, "{ ... }"); + } + const info = @typeInfo(T).Union; + if (info.tag_type) |UnionTagType| { + try output(context, "{ ."); + try output(context, @tagName(UnionTagType(value))); + try output(context, " = "); + inline for (info.fields) |u_field| { + if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { + try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth - 1); } - }, - else => unreachable, + } + try output(context, " }"); + } else { + try format(context, Errors, output, "@{x}", @ptrToInt(&value)); } - return; }, - builtin.TypeId.Pointer => |ptr_info| switch (ptr_info.size) { - builtin.TypeInfo.Pointer.Size.One => switch (@typeInfo(ptr_info.child)) { + .Struct => { + if (comptime std.meta.trait.hasFn("format")(T)) { + return value.format(fmt, context, Errors, output); + } + + try output(context, @typeName(T)); + if (max_depth == 0) { + return output(context, "{ ... }"); + } + comptime var field_i = 0; + inline while (field_i < @memberCount(T)) : (field_i += 1) { + if (field_i == 0) { + try output(context, "{ ."); + } else { + try output(context, ", ."); + } + try output(context, @memberName(T, field_i)); + try output(context, " = "); + try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth - 1); + } + try output(context, " }"); + }, + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => switch (@typeInfo(ptr_info.child)) { builtin.TypeId.Array => |info| { if (info.child == u8) { return formatText(value, fmt, context, Errors, output); @@ -207,7 +218,7 @@ pub fn formatType( }, else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), }, - builtin.TypeInfo.Pointer.Size.Many => { + .Many => { if (ptr_info.child == u8) { if (fmt.len > 0 and fmt[0] == 's') { const len = mem.len(u8, value); @@ -216,7 +227,7 @@ pub fn formatType( } return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); }, - builtin.TypeInfo.Pointer.Size.Slice => { + .Slice => { if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { return formatText(value, fmt, context, Errors, output); } @@ -225,17 +236,17 @@ pub fn formatType( } return format(context, Errors, output, "{}@{x}", @typeName(ptr_info.child), @ptrToInt(value.ptr)); }, - builtin.TypeInfo.Pointer.Size.C => { + .C => { return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); }, }, - builtin.TypeId.Array => |info| { + .Array => |info| { if (info.child == u8) { return formatText(value, fmt, context, Errors, output); } return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(&value)); }, - builtin.TypeId.Fn => { + .Fn => { return format(context, Errors, output, "{}@{x}", @typeName(T), @ptrToInt(value)); }, else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), @@ -249,24 +260,24 @@ fn formatValue( comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { - if (fmt.len > 0) { - if (fmt[0] == 'B') { - comptime var width: ?usize = null; - if (fmt.len > 1) { - if (fmt[1] == 'i') { - if (fmt.len > 2) width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable); - return formatBytes(value, width, 1024, context, Errors, output); + if (fmt.len > 0 and fmt[0] == 'B') { + comptime var width: ?usize = null; + if (fmt.len > 1) { + if (fmt[1] == 'i') { + if (fmt.len > 2) { + width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable); } - width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); + return formatBytes(value, width, 1024, context, Errors, output); } - return formatBytes(value, width, 1000, context, Errors, output); + width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); } + return formatBytes(value, width, 1000, context, Errors, output); } const T = @typeOf(value); switch (@typeId(T)) { - builtin.TypeId.Float => return formatFloatValue(value, fmt, context, Errors, output), - builtin.TypeId.Int, builtin.TypeId.ComptimeInt => return formatIntValue(value, fmt, context, Errors, output), + .Float => return formatFloatValue(value, fmt, context, Errors, output), + .Int, .ComptimeInt => return formatIntValue(value, fmt, context, Errors, output), else => comptime unreachable, } } @@ -797,7 +808,7 @@ pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T { } } -test "fmt.parseInt" { +test "parseInt" { testing.expect((parseInt(i32, "-10", 10) catch unreachable) == -10); testing.expect((parseInt(i32, "+10", 10) catch unreachable) == 10); testing.expect(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidCharacter); @@ -828,7 +839,7 @@ pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsigned return x; } -test "fmt.parseUnsigned" { +test "parseUnsigned" { testing.expect((try parseUnsigned(u16, "050124", 10)) == 50124); testing.expect((try parseUnsigned(u16, "65535", 10)) == 65535); testing.expectError(error.Overflow, parseUnsigned(u16, "65536", 10)); @@ -913,7 +924,7 @@ fn countSize(size: *usize, bytes: []const u8) (error{}!void) { size.* += bytes.len; } -test "buf print int" { +test "bufPrintInt" { var buffer: [100]u8 = undefined; const buf = buffer[0..]; testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, 0), "-101111000110000101001110")); @@ -949,7 +960,7 @@ test "parse unsigned comptime" { } } -test "fmt.format" { +test "fmt.optional" { { const value: ?i32 = 1234; try testFmt("optional: 1234\n", "optional: {}\n", value); @@ -958,6 +969,9 @@ test "fmt.format" { const value: ?i32 = null; try testFmt("optional: null\n", "optional: {}\n", value); } +} + +test "fmt.error" { { const value: anyerror!i32 = 1234; try testFmt("error union: 1234\n", "error union: {}\n", value); @@ -966,10 +980,16 @@ test "fmt.format" { const value: anyerror!i32 = error.InvalidChar; try testFmt("error union: error.InvalidChar\n", "error union: {}\n", value); } +} + +test "fmt.int.small" { { const value: u3 = 0b101; try testFmt("u3: 5\n", "u3: {}\n", value); } +} + +test "fmt.int.specifier" { { const value: u8 = 'a'; try testFmt("u8: a\n", "u8: {c}\n", value); @@ -978,6 +998,9 @@ test "fmt.format" { const value: u8 = 0b1100; try testFmt("u8: 0b1100\n", "u8: 0b{b}\n", value); } +} + +test "fmt.buffer" { { var buf1: [32]u8 = undefined; var context = BufPrintContext{ .remaining = buf1[0..] }; @@ -995,6 +1018,9 @@ test "fmt.format" { res = buf1[0 .. buf1.len - context.remaining.len]; testing.expect(mem.eql(u8, res, "1100")); } +} + +test "fmt.array" { { const value: [3]u8 = "abc"; try testFmt("array: abc\n", "array: {}\n", value); @@ -1007,6 +1033,9 @@ test "fmt.format" { &value, ); } +} + +test "fmt.slice" { { const value: []const u8 = "abc"; try testFmt("slice: abc\n", "slice: {}\n", value); @@ -1015,6 +1044,12 @@ test "fmt.format" { const value = @intToPtr([*]const []const u8, 0xdeadbeef)[0..0]; try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", value); } + + try testFmt("buf: Test \n", "buf: {s5}\n", "Test"); + try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test"); +} + +test "fmt.pointer" { { const value = @intToPtr(*i32, 0xdeadbeef); try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value); @@ -1028,12 +1063,19 @@ test "fmt.format" { const value = @intToPtr(fn () void, 0xdeadbeef); try testFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", value); } - try testFmt("buf: Test \n", "buf: {s5}\n", "Test"); - try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test"); +} + +test "fmt.cstr" { try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C"); try testFmt("cstr: Test C \n", "cstr: {s10}\n", c"Test C"); +} + +test "fmt.filesize" { try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024)); try testFmt("file size: 66.06MB\n", "file size: {B2}\n", usize(63 * 1024 * 1024)); +} + +test "fmt.struct" { { const Struct = struct { field: u8, @@ -1050,15 +1092,19 @@ test "fmt.format" { const value = Struct{ .a = 0, .b = 1 }; try testFmt("struct: Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", value); } - { - const Enum = enum { - One, - Two, - }; - const value = Enum.Two; - try testFmt("enum: Enum.Two\n", "enum: {}\n", value); - try testFmt("enum: Enum.Two\n", "enum: {}\n", &value); - } +} + +test "fmt.enum" { + const Enum = enum { + One, + Two, + }; + const value = Enum.Two; + try testFmt("enum: Enum.Two\n", "enum: {}\n", value); + try testFmt("enum: Enum.Two\n", "enum: {}\n", &value); +} + +test "fmt.float.scientific" { { var buf1: [32]u8 = undefined; const value: f32 = 1.34; @@ -1088,6 +1134,9 @@ test "fmt.format" { testing.expect(mem.eql(u8, result, "f64: 9.99996e-40\n")); } } +} + +test "fmt.float.scientific.precision" { { var buf1: [32]u8 = undefined; const value: f64 = 1.409706e-42; @@ -1114,6 +1163,9 @@ test "fmt.format" { const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); testing.expect(mem.eql(u8, result, "f64: 1.00001e+05\n")); } +} + +test "fmt.float.special" { { var buf1: [32]u8 = undefined; const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64); @@ -1136,6 +1188,9 @@ test "fmt.format" { const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64); testing.expect(mem.eql(u8, result, "f64: -inf\n")); } +} + +test "fmt.float.decimal" { { var buf1: [64]u8 = undefined; const value: f64 = 1.52314e+29; @@ -1216,7 +1271,9 @@ test "fmt.format" { const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); testing.expect(mem.eql(u8, result, "f64: 0.00000\n")); } - // libc checks +} + +test "fmt.float.libc.sanity" { { var buf1: [32]u8 = undefined; const value: f64 = f64(@bitCast(f32, u32(916964781))); @@ -1267,127 +1324,127 @@ test "fmt.format" { const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); testing.expect(mem.eql(u8, result, "f64: 18014400656965630.00000\n")); } - //custom type format - { - const Vec2 = struct { - const SelfType = @This(); - x: f32, - y: f32, - - pub fn format( - self: SelfType, - comptime fmt: []const u8, - context: var, - comptime Errors: type, - output: fn (@typeOf(context), []const u8) Errors!void, - ) Errors!void { - switch (fmt.len) { - 0 => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), - 1 => switch (fmt[0]) { - //point format - 'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), - //dimension format - 'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y), - else => unreachable, - }, +} + +test "fmt.custom" { + const Vec2 = struct { + const SelfType = @This(); + x: f32, + y: f32, + + pub fn format( + self: SelfType, + comptime fmt: []const u8, + context: var, + comptime Errors: type, + output: fn (@typeOf(context), []const u8) Errors!void, + ) Errors!void { + switch (fmt.len) { + 0 => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), + 1 => switch (fmt[0]) { + //point format + 'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), + //dimension format + 'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y), else => unreachable, - } + }, + else => unreachable, } - }; + } + }; - var buf1: [32]u8 = undefined; - var value = Vec2{ - .x = 10.2, - .y = 2.22, - }; - try testFmt("point: (10.200,2.220)\n", "point: {}\n", &value); - try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", &value); + var buf1: [32]u8 = undefined; + var value = Vec2{ + .x = 10.2, + .y = 2.22, + }; + try testFmt("point: (10.200,2.220)\n", "point: {}\n", &value); + try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", &value); - // same thing but not passing a pointer - try testFmt("point: (10.200,2.220)\n", "point: {}\n", value); - try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value); - } - //struct format - { - const S = struct { - a: u32, - b: anyerror, - }; + // same thing but not passing a pointer + try testFmt("point: (10.200,2.220)\n", "point: {}\n", value); + try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value); +} - const inst = S{ - .a = 456, - .b = error.Unused, - }; +test "fmt.struct" { + const S = struct { + a: u32, + b: anyerror, + }; - try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst); - } - //union format - { - const TU = union(enum) { - float: f32, - int: u32, - }; + const inst = S{ + .a = 456, + .b = error.Unused, + }; - const UU = union { - float: f32, - int: u32, - }; + try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst); +} - const EU = extern union { - float: f32, - int: u32, - }; +test "fmt.union" { + const TU = union(enum) { + float: f32, + int: u32, + }; - const tu_inst = TU{ .int = 123 }; - const uu_inst = UU{ .int = 456 }; - const eu_inst = EU{ .float = 321.123 }; + const UU = union { + float: f32, + int: u32, + }; - try testFmt("TU{ .int = 123 }", "{}", tu_inst); + const EU = extern union { + float: f32, + int: u32, + }; - var buf: [100]u8 = undefined; - const uu_result = try bufPrint(buf[0..], "{}", uu_inst); - testing.expect(mem.eql(u8, uu_result[0..3], "UU@")); + const tu_inst = TU{ .int = 123 }; + const uu_inst = UU{ .int = 456 }; + const eu_inst = EU{ .float = 321.123 }; - const eu_result = try bufPrint(buf[0..], "{}", eu_inst); - testing.expect(mem.eql(u8, uu_result[0..3], "EU@")); - } - //enum format - { - const E = enum { - One, - Two, - Three, - }; + try testFmt("TU{ .int = 123 }", "{}", tu_inst); - const inst = E.Two; + var buf: [100]u8 = undefined; + const uu_result = try bufPrint(buf[0..], "{}", uu_inst); + testing.expect(mem.eql(u8, uu_result[0..3], "UU@")); - try testFmt("E.Two", "{}", inst); - } - //self-referential struct format - { - const S = struct { - const SelfType = @This(); - a: ?*SelfType, - }; + const eu_result = try bufPrint(buf[0..], "{}", eu_inst); + testing.expect(mem.eql(u8, uu_result[0..3], "EU@")); +} - var inst = S{ - .a = null, - }; - inst.a = &inst; +test "fmt.enum" { + const E = enum { + One, + Two, + Three, + }; - try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst); - } - //print bytes as hex - { - const some_bytes = "\xCA\xFE\xBA\xBE"; - try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes); - try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes); - //Test Slices - try testFmt("uppercase: CAFE\n", "uppercase: {X}\n", some_bytes[0..2]); - try testFmt("lowercase: babe\n", "lowercase: {x}\n", some_bytes[2..]); - const bytes_with_zeros = "\x00\x0E\xBA\xBE"; - try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", bytes_with_zeros); - } + const inst = E.Two; + + try testFmt("E.Two", "{}", inst); +} + +test "fmt.struct.self-referential" { + const S = struct { + const SelfType = @This(); + a: ?*SelfType, + }; + + var inst = S{ + .a = null, + }; + inst.a = &inst; + + try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst); +} + +test "fmt.bytes.hex" { + const some_bytes = "\xCA\xFE\xBA\xBE"; + try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes); + try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes); + //Test Slices + try testFmt("uppercase: CAFE\n", "uppercase: {X}\n", some_bytes[0..2]); + try testFmt("lowercase: babe\n", "lowercase: {x}\n", some_bytes[2..]); + const bytes_with_zeros = "\x00\x0E\xBA\xBE"; + try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", bytes_with_zeros); } fn testFmt(expected: []const u8, comptime template: []const u8, args: ...) !void { |
