| ... | @@ -8,6 +8,8 @@ const builtin = @import("builtin"); | ... | @@ -8,6 +8,8 @@ const builtin = @import("builtin"); |
| 8 | const errol = @import("fmt/errol.zig"); | 8 | const errol = @import("fmt/errol.zig"); |
| 9 | const lossyCast = std.math.lossyCast; | 9 | const lossyCast = std.math.lossyCast; |
| 10 | | 10 | |
| | 11 | pub const default_max_depth = 3; |
| | 12 | |
| 11 | /// Renders fmt string with args, calling output with slices of bytes. | 13 | /// Renders fmt string with args, calling output with slices of bytes. |
| 12 | /// If `output` returns an error, the error is returned from `format` and | 14 | /// If `output` returns an error, the error is returned from `format` and |
| 13 | /// `output` is not called again. | 15 | /// `output` is not called again. |
| ... | @@ -49,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), | ... | @@ -49,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), |
| 49 | start_index = i; | 51 | start_index = i; |
| 50 | }, | 52 | }, |
| 51 | '}' => { | 53 | '}' => { |
| 52 | try formatType(args[next_arg], fmt[0..0], context, Errors, output); | 54 | try formatType(args[next_arg], fmt[0..0], context, Errors, output, default_max_depth); |
| 53 | next_arg += 1; | 55 | next_arg += 1; |
| 54 | state = State.Start; | 56 | state = State.Start; |
| 55 | start_index = i + 1; | 57 | start_index = i + 1; |
| ... | @@ -69,7 +71,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), | ... | @@ -69,7 +71,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), |
| 69 | State.FormatString => switch (c) { | 71 | State.FormatString => switch (c) { |
| 70 | '}' => { | 72 | '}' => { |
| 71 | const s = start_index + 1; | 73 | const s = start_index + 1; |
| 72 | try formatType(args[next_arg], fmt[s..i], context, Errors, output); | 74 | try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth); |
| 73 | next_arg += 1; | 75 | next_arg += 1; |
| 74 | state = State.Start; | 76 | state = State.Start; |
| 75 | start_index = i + 1; | 77 | start_index = i + 1; |
| ... | @@ -108,6 +110,7 @@ pub fn formatType( | ... | @@ -108,6 +110,7 @@ pub fn formatType( |
| 108 | context: var, | 110 | context: var, |
| 109 | comptime Errors: type, | 111 | comptime Errors: type, |
| 110 | output: fn (@typeOf(context), []const u8) Errors!void, | 112 | output: fn (@typeOf(context), []const u8) Errors!void, |
| | 113 | max_depth: usize, |
| 111 | ) Errors!void { | 114 | ) Errors!void { |
| 112 | const T = @typeOf(value); | 115 | const T = @typeOf(value); |
| 113 | switch (@typeInfo(T)) { | 116 | switch (@typeInfo(T)) { |
| ... | @@ -122,16 +125,16 @@ pub fn formatType( | ... | @@ -122,16 +125,16 @@ pub fn formatType( |
| 122 | }, | 125 | }, |
| 123 | builtin.TypeId.Optional => { | 126 | builtin.TypeId.Optional => { |
| 124 | if (value) |payload| { | 127 | if (value) |payload| { |
| 125 | return formatType(payload, fmt, context, Errors, output); | 128 | return formatType(payload, fmt, context, Errors, output, max_depth); |
| 126 | } else { | 129 | } else { |
| 127 | return output(context, "null"); | 130 | return output(context, "null"); |
| 128 | } | 131 | } |
| 129 | }, | 132 | }, |
| 130 | builtin.TypeId.ErrorUnion => { | 133 | builtin.TypeId.ErrorUnion => { |
| 131 | if (value) |payload| { | 134 | if (value) |payload| { |
| 132 | return formatType(payload, fmt, context, Errors, output); | 135 | return formatType(payload, fmt, context, Errors, output, max_depth); |
| 133 | } else |err| { | 136 | } else |err| { |
| 134 | return formatType(err, fmt, context, Errors, output); | 137 | return formatType(err, fmt, context, Errors, output, max_depth); |
| 135 | } | 138 | } |
| 136 | }, | 139 | }, |
| 137 | builtin.TypeId.ErrorSet => { | 140 | builtin.TypeId.ErrorSet => { |
| ... | @@ -164,10 +167,13 @@ pub fn formatType( | ... | @@ -164,10 +167,13 @@ pub fn formatType( |
| 164 | switch (comptime @typeId(T)) { | 167 | switch (comptime @typeId(T)) { |
| 165 | builtin.TypeId.Enum => { | 168 | builtin.TypeId.Enum => { |
| 166 | try output(context, "."); | 169 | try output(context, "."); |
| 167 | try formatType(@tagName(value), "", context, Errors, output); | 170 | try formatType(@tagName(value), "", context, Errors, output, max_depth); |
| 168 | return; | 171 | return; |
| 169 | }, | 172 | }, |
| 170 | builtin.TypeId.Struct => { | 173 | builtin.TypeId.Struct => { |
| | 174 | if (max_depth == 0) { |
| | 175 | return output(context, "{ ... }"); |
| | 176 | } |
| 171 | comptime var field_i = 0; | 177 | comptime var field_i = 0; |
| 172 | inline while (field_i < @memberCount(T)) : (field_i += 1) { | 178 | inline while (field_i < @memberCount(T)) : (field_i += 1) { |
| 173 | if (field_i == 0) { | 179 | if (field_i == 0) { |
| ... | @@ -177,11 +183,14 @@ pub fn formatType( | ... | @@ -177,11 +183,14 @@ pub fn formatType( |
| 177 | } | 183 | } |
| 178 | try output(context, @memberName(T, field_i)); | 184 | try output(context, @memberName(T, field_i)); |
| 179 | try output(context, " = "); | 185 | try output(context, " = "); |
| 180 | try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output); | 186 | try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth-1); |
| 181 | } | 187 | } |
| 182 | try output(context, " }"); | 188 | try output(context, " }"); |
| 183 | }, | 189 | }, |
| 184 | builtin.TypeId.Union => { | 190 | builtin.TypeId.Union => { |
| | 191 | if (max_depth == 0) { |
| | 192 | return output(context, "{ ... }"); |
| | 193 | } |
| 185 | const info = @typeInfo(T).Union; | 194 | const info = @typeInfo(T).Union; |
| 186 | if (info.tag_type) |UnionTagType| { | 195 | if (info.tag_type) |UnionTagType| { |
| 187 | try output(context, "{ ."); | 196 | try output(context, "{ ."); |
| ... | @@ -189,7 +198,7 @@ pub fn formatType( | ... | @@ -189,7 +198,7 @@ pub fn formatType( |
| 189 | try output(context, " = "); | 198 | try output(context, " = "); |
| 190 | inline for (info.fields) |u_field| { | 199 | inline for (info.fields) |u_field| { |
| 191 | if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { | 200 | if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { |
| 192 | try formatType(@field(value, u_field.name), "", context, Errors, output); | 201 | try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth-1); |
| 193 | } | 202 | } |
| 194 | } | 203 | } |
| 195 | try output(context, " }"); | 204 | try output(context, " }"); |
| ... | @@ -210,7 +219,7 @@ pub fn formatType( | ... | @@ -210,7 +219,7 @@ pub fn formatType( |
| 210 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); | 219 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); |
| 211 | }, | 220 | }, |
| 212 | builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { | 221 | builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { |
| 213 | return formatType(value.*, fmt, context, Errors, output); | 222 | return formatType(value.*, fmt, context, Errors, output, max_depth); |
| 214 | }, | 223 | }, |
| 215 | else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), | 224 | else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), |
| 216 | }, | 225 | }, |
| ... | @@ -986,17 +995,17 @@ test "fmt.format" { | ... | @@ -986,17 +995,17 @@ test "fmt.format" { |
| 986 | { | 995 | { |
| 987 | var buf1: [32]u8 = undefined; | 996 | var buf1: [32]u8 = undefined; |
| 988 | var context = BufPrintContext{ .remaining = buf1[0..] }; | 997 | var context = BufPrintContext{ .remaining = buf1[0..] }; |
| 989 | try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite); | 998 | try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); |
| 990 | var res = buf1[0 .. buf1.len - context.remaining.len]; | 999 | var res = buf1[0 .. buf1.len - context.remaining.len]; |
| 991 | testing.expect(mem.eql(u8, res, "1234")); | 1000 | testing.expect(mem.eql(u8, res, "1234")); |
| 992 | | 1001 | |
| 993 | context = BufPrintContext{ .remaining = buf1[0..] }; | 1002 | context = BufPrintContext{ .remaining = buf1[0..] }; |
| 994 | try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite); | 1003 | try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); |
| 995 | res = buf1[0 .. buf1.len - context.remaining.len]; | 1004 | res = buf1[0 .. buf1.len - context.remaining.len]; |
| 996 | testing.expect(mem.eql(u8, res, "a")); | 1005 | testing.expect(mem.eql(u8, res, "a")); |
| 997 | | 1006 | |
| 998 | context = BufPrintContext{ .remaining = buf1[0..] }; | 1007 | context = BufPrintContext{ .remaining = buf1[0..] }; |
| 999 | try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite); | 1008 | try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); |
| 1000 | res = buf1[0 .. buf1.len - context.remaining.len]; | 1009 | res = buf1[0 .. buf1.len - context.remaining.len]; |
| 1001 | testing.expect(mem.eql(u8, res, "1100")); | 1010 | testing.expect(mem.eql(u8, res, "1100")); |
| 1002 | } | 1011 | } |
| ... | @@ -1364,6 +1373,20 @@ test "fmt.format" { | ... | @@ -1364,6 +1373,20 @@ test "fmt.format" { |
| 1364 | | 1373 | |
| 1365 | try testFmt("E.Two", "{}", inst); | 1374 | try testFmt("E.Two", "{}", inst); |
| 1366 | } | 1375 | } |
| | 1376 | //self-referential struct format |
| | 1377 | { |
| | 1378 | const S = struct { |
| | 1379 | const SelfType = @This(); |
| | 1380 | a: ?*SelfType, |
| | 1381 | }; |
| | 1382 | |
| | 1383 | var inst = S{ |
| | 1384 | .a = null, |
| | 1385 | }; |
| | 1386 | inst.a = &inst; |
| | 1387 | |
| | 1388 | try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst); |
| | 1389 | } |
| 1367 | //print bytes as hex | 1390 | //print bytes as hex |
| 1368 | { | 1391 | { |
| 1369 | const some_bytes = "\xCA\xFE\xBA\xBE"; | 1392 | const some_bytes = "\xCA\xFE\xBA\xBE"; |
| ... | @@ -1449,3 +1472,64 @@ test "fmt.formatIntValue with comptime_int" { | ... | @@ -1449,3 +1472,64 @@ test "fmt.formatIntValue with comptime_int" { |
| 1449 | try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); | 1472 | try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); |
| 1450 | assert(mem.eql(u8, buf.toSlice(), "123456789123456789")); | 1473 | assert(mem.eql(u8, buf.toSlice(), "123456789123456789")); |
| 1451 | } | 1474 | } |
| | 1475 | |
| | 1476 | test "fmt.formatType max_depth" { |
| | 1477 | const Vec2 = struct { |
| | 1478 | const SelfType = @This(); |
| | 1479 | x: f32, |
| | 1480 | y: f32, |
| | 1481 | |
| | 1482 | pub fn format( |
| | 1483 | self: SelfType, |
| | 1484 | comptime fmt: []const u8, |
| | 1485 | context: var, |
| | 1486 | comptime Errors: type, |
| | 1487 | output: fn (@typeOf(context), []const u8) Errors!void, |
| | 1488 | ) Errors!void { |
| | 1489 | return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y); |
| | 1490 | } |
| | 1491 | }; |
| | 1492 | const E = enum { |
| | 1493 | One, |
| | 1494 | Two, |
| | 1495 | Three, |
| | 1496 | }; |
| | 1497 | const TU = union(enum) { |
| | 1498 | const SelfType = @This(); |
| | 1499 | float: f32, |
| | 1500 | int: u32, |
| | 1501 | ptr: ?*SelfType, |
| | 1502 | }; |
| | 1503 | const S = struct { |
| | 1504 | const SelfType = @This(); |
| | 1505 | a: ?*SelfType, |
| | 1506 | tu: TU, |
| | 1507 | e: E, |
| | 1508 | vec: Vec2, |
| | 1509 | }; |
| | 1510 | |
| | 1511 | var inst = S{ |
| | 1512 | .a = null, |
| | 1513 | .tu = TU{ .ptr = null }, |
| | 1514 | .e = E.Two, |
| | 1515 | .vec = Vec2{ .x = 10.2, .y = 2.22 }, |
| | 1516 | }; |
| | 1517 | inst.a = &inst; |
| | 1518 | inst.tu.ptr = &inst.tu; |
| | 1519 | |
| | 1520 | var buf0 = try std.Buffer.init(std.debug.global_allocator, ""); |
| | 1521 | try formatType(inst, "", &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0); |
| | 1522 | assert(mem.eql(u8, buf0.toSlice(), "S{ ... }")); |
| | 1523 | |
| | 1524 | var buf1 = try std.Buffer.init(std.debug.global_allocator, ""); |
| | 1525 | try formatType(inst, "", &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1); |
| | 1526 | assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); |
| | 1527 | |
| | 1528 | var buf2 = try std.Buffer.init(std.debug.global_allocator, ""); |
| | 1529 | try formatType(inst, "", &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2); |
| | 1530 | assert(mem.eql(u8, buf2.toSlice(), "S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }")); |
| | 1531 | |
| | 1532 | var buf3 = try std.Buffer.init(std.debug.global_allocator, ""); |
| | 1533 | try formatType(inst, "", &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3); |
| | 1534 | assert(mem.eql(u8, buf3.toSlice(), "S{ .a = S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ .ptr = TU{ ... } } }, .e = E.Two, .vec = (10.200,2.220) }")); |
| | 1535 | } |