| ... | ... | @@ -8,6 +8,8 @@ const builtin = @import("builtin"); |
| 8 | 8 | const errol = @import("fmt/errol.zig"); |
| 9 | 9 | const lossyCast = std.math.lossyCast; |
| 10 | 10 | |
| 11 | pub const default_max_depth = 3; |
| 12 | |
| 11 | 13 | /// Renders fmt string with args, calling output with slices of bytes. |
| 12 | 14 | /// If `output` returns an error, the error is returned from `format` and |
| 13 | 15 | /// `output` is not called again. |
| ... | ... | @@ -49,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), |
| 49 | 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 | 55 | next_arg += 1; |
| 54 | 56 | state = State.Start; |
| 55 | 57 | start_index = i + 1; |
| ... | ... | @@ -69,7 +71,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), |
| 69 | 71 | State.FormatString => switch (c) { |
| 70 | 72 | '}' => { |
| 71 | 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 | 75 | next_arg += 1; |
| 74 | 76 | state = State.Start; |
| 75 | 77 | start_index = i + 1; |
| ... | ... | @@ -108,6 +110,7 @@ pub fn formatType( |
| 108 | 110 | context: var, |
| 109 | 111 | comptime Errors: type, |
| 110 | 112 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 113 | max_depth: usize, |
| 111 | 114 | ) Errors!void { |
| 112 | 115 | const T = @typeOf(value); |
| 113 | 116 | switch (@typeInfo(T)) { |
| ... | ... | @@ -122,16 +125,16 @@ pub fn formatType( |
| 122 | 125 | }, |
| 123 | 126 | builtin.TypeId.Optional => { |
| 124 | 127 | if (value) |payload| { |
| 125 | | return formatType(payload, fmt, context, Errors, output); |
| 128 | return formatType(payload, fmt, context, Errors, output, max_depth); |
| 126 | 129 | } else { |
| 127 | 130 | return output(context, "null"); |
| 128 | 131 | } |
| 129 | 132 | }, |
| 130 | 133 | builtin.TypeId.ErrorUnion => { |
| 131 | 134 | if (value) |payload| { |
| 132 | | return formatType(payload, fmt, context, Errors, output); |
| 135 | return formatType(payload, fmt, context, Errors, output, max_depth); |
| 133 | 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 | 140 | builtin.TypeId.ErrorSet => { |
| ... | ... | @@ -164,10 +167,13 @@ pub fn formatType( |
| 164 | 167 | switch (comptime @typeId(T)) { |
| 165 | 168 | builtin.TypeId.Enum => { |
| 166 | 169 | try output(context, "."); |
| 167 | | try formatType(@tagName(value), "", context, Errors, output); |
| 170 | try formatType(@tagName(value), "", context, Errors, output, max_depth); |
| 168 | 171 | return; |
| 169 | 172 | }, |
| 170 | 173 | builtin.TypeId.Struct => { |
| 174 | if (max_depth == 0) { |
| 175 | return output(context, "{ ... }"); |
| 176 | } |
| 171 | 177 | comptime var field_i = 0; |
| 172 | 178 | inline while (field_i < @memberCount(T)) : (field_i += 1) { |
| 173 | 179 | if (field_i == 0) { |
| ... | ... | @@ -177,11 +183,14 @@ pub fn formatType( |
| 177 | 183 | } |
| 178 | 184 | try output(context, @memberName(T, field_i)); |
| 179 | 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 | 188 | try output(context, " }"); |
| 183 | 189 | }, |
| 184 | 190 | builtin.TypeId.Union => { |
| 191 | if (max_depth == 0) { |
| 192 | return output(context, "{ ... }"); |
| 193 | } |
| 185 | 194 | const info = @typeInfo(T).Union; |
| 186 | 195 | if (info.tag_type) |UnionTagType| { |
| 187 | 196 | try output(context, "{ ."); |
| ... | ... | @@ -189,7 +198,7 @@ pub fn formatType( |
| 189 | 198 | try output(context, " = "); |
| 190 | 199 | inline for (info.fields) |u_field| { |
| 191 | 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 | 204 | try output(context, " }"); |
| ... | ... | @@ -210,7 +219,7 @@ pub fn formatType( |
| 210 | 219 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); |
| 211 | 220 | }, |
| 212 | 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 | 224 | else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), |
| 216 | 225 | }, |
| ... | ... | @@ -986,17 +995,17 @@ test "fmt.format" { |
| 986 | 995 | { |
| 987 | 996 | var buf1: [32]u8 = undefined; |
| 988 | 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 | 999 | var res = buf1[0 .. buf1.len - context.remaining.len]; |
| 991 | 1000 | testing.expect(mem.eql(u8, res, "1234")); |
| 992 | 1001 | |
| 993 | 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 | 1004 | res = buf1[0 .. buf1.len - context.remaining.len]; |
| 996 | 1005 | testing.expect(mem.eql(u8, res, "a")); |
| 997 | 1006 | |
| 998 | 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 | 1009 | res = buf1[0 .. buf1.len - context.remaining.len]; |
| 1001 | 1010 | testing.expect(mem.eql(u8, res, "1100")); |
| 1002 | 1011 | } |
| ... | ... | @@ -1364,6 +1373,20 @@ test "fmt.format" { |
| 1364 | 1373 | |
| 1365 | 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 | 1390 | //print bytes as hex |
| 1368 | 1391 | { |
| 1369 | 1392 | const some_bytes = "\xCA\xFE\xBA\xBE"; |
| ... | ... | @@ -1449,3 +1472,64 @@ test "fmt.formatIntValue with comptime_int" { |
| 1449 | 1472 | try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); |
| 1450 | 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 | } |