├── .gitignore ├── images └── screenshot.png ├── examples ├── utils.zig ├── struct_with_all_types.zig └── examples.zig ├── src ├── tree-fmt.zig ├── ansi-esc-code.zig ├── tree-formatter-settings.zig └── tree-formatter.zig ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache/ 2 | zig-out/ 3 | -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/speed2exe/tree-fmt/HEAD/images/screenshot.png -------------------------------------------------------------------------------- /examples/utils.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const tree_fmt = @import("tree-fmt"); 3 | const treeFormatter = tree_fmt.treeFormatter; 4 | const TreeFormatter = tree_fmt.TreeFormatter; 5 | pub const formatter = blk: { 6 | break :blk treeFormatter(std.testing.allocator, std.io.getStdErr().writer()); 7 | }; 8 | -------------------------------------------------------------------------------- /src/tree-fmt.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const tree_formatter = @import("./tree-formatter.zig"); 3 | pub const treeFormatter = tree_formatter.treeFormatter; 4 | pub const TreeFormatter = tree_formatter.TreeFormatter; 5 | pub const TreeFormatterSettings = @import("./tree-formatter-settings.zig").TreeFormatterSettings; 6 | 7 | // Create a TreeFormatter with: 8 | // - default settings from TreeFormatterSettings 9 | // - use gpa as the allocator 10 | // - std.io.getStdErr().writer() as the writer 11 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 12 | const allocator = gpa.allocator(); 13 | pub fn defaultFormatter() TreeFormatter(@TypeOf(std.io.getStdErr().writer())) { 14 | return treeFormatter(allocator, std.io.getStdErr().writer()); 15 | } 16 | -------------------------------------------------------------------------------- /src/ansi-esc-code.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const reset = "\x1b[m"; 3 | 4 | pub fn comptimeFmtInColor( 5 | comptime color: Color, 6 | comptime fmt: []const u8, 7 | args: anytype, 8 | ) []const u8 { 9 | return std.fmt.comptimePrint(color.toEscapeCode() ++ fmt ++ reset, args); 10 | } 11 | 12 | pub fn comptimeInColor( 13 | comptime color: Color, 14 | comptime fmt: []const u8, 15 | ) []const u8 { 16 | return comptime color.toEscapeCode() ++ fmt ++ reset; 17 | } 18 | 19 | pub const Color = enum { 20 | bright_black, 21 | cyan, 22 | yellow, 23 | blue, 24 | green, 25 | 26 | inline fn toEscapeCode(self: Color) []const u8 { 27 | return switch (self) { 28 | inline .bright_black => "\x1b[90m", 29 | inline .cyan => "\x1b[36m", 30 | inline .yellow => "\x1b[33m", 31 | inline .blue => "\x1b[34m", 32 | inline .green => "\x1b[32m", 33 | }; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/tree-formatter-settings.zig: -------------------------------------------------------------------------------- 1 | pub const TreeFormatterSettings = struct { 2 | // name of the root node 3 | name: []const u8 = ".", 4 | 5 | array_elem_limit: usize = 5, 6 | slice_elem_limit: usize = 5, 7 | vector_elem_limit: usize = 5, 8 | hash_map_entry_limit: usize = 5, 9 | multi_array_list_get_limit: usize = 5, 10 | 11 | /// if []u8 should be printed as a string 12 | /// affects Arrays, Slice, Vector 13 | print_u8_chars: bool = true, 14 | 15 | /// disables printing of []u8 16 | /// in lists (Array, Slice, Vector) 17 | ignore_u8_in_lists: bool = false, 18 | 19 | /// determines maximum repeat count for a unique pointer 20 | ptr_repeat_limit: usize = 1, 21 | 22 | /// limits the number of children to print 23 | type_print_limit: usize = 1, //TODO 24 | 25 | /// if allowed to format multi array list (std.MultiArrayList) 26 | format_multi_array_list: bool = true, 27 | 28 | /// if allowed to format hash map (std.HashMap) 29 | format_hash_map_unmanaged: bool = true, 30 | }; 31 | -------------------------------------------------------------------------------- /examples/struct_with_all_types.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const utils = @import("./utils.zig"); 3 | const formatter = utils.formatter; 4 | 5 | var i32_value: i32 = 42; 6 | 7 | const Struct1 = struct { 8 | // valid in comptime 9 | // k: type = u16, 10 | // field_comptime_float: comptime_float = 3.14, 11 | // field_comptime_int: comptime_int = 11, 12 | // field_null: @TypeOf(null) = null, 13 | // field_fn: fn () void = functionOne, 14 | 15 | field_void: void = undefined, 16 | field_bool: bool = true, 17 | field_u8: u32 = 11, 18 | field_float: f32 = 3.14, 19 | 20 | field_i32_ptr: *i32 = &i32_value, 21 | field_slice_u8: []const u8 = "s1 string", 22 | 23 | field_array_u8: [3]u8 = [_]u8{ 1, 2, 3 }, 24 | field_array_u8_empty: [0]u8 = .{}, 25 | 26 | field_struct2: Struct2 = .{}, 27 | field_struct4: Struct4 = .{}, 28 | 29 | // TODO: not working, need to fix 30 | // field_struct_recursive: LinkedNode, 31 | 32 | field_opt_i32_value: ?i32 = 9, 33 | field_opt_i32_null: ?i32 = null, 34 | 35 | field_error: ErrorSet1 = error.Error1, 36 | field_error_union_error: anyerror!u8 = error.Error2, 37 | field_error_union_value: ErrorSet1!u8 = 5, 38 | 39 | field_enum_1: EnumSet1 = .Enum1, 40 | field_enum_2: EnumSet2 = .Enum3, 41 | 42 | field_union_1: Union1 = .{ .int = 98 }, 43 | field_union_2: Union1 = .{ .float = 3.14 }, 44 | field_union_3: Union1 = .{ .bool = true }, 45 | 46 | field_tagged_union_1: TaggedUnion1 = .{ .int = 98 }, 47 | field_tagged_union_2: TaggedUnion1 = .{ .float = 3.14 }, 48 | field_tagged_union_3: TaggedUnion1 = .{ .bool = true }, 49 | 50 | field_fn_ptr: *const fn () void = functionOne, 51 | field_vector: @Vector(4, i32) = .{ 1, 2, 3, 4 }, 52 | 53 | // TODO: support Frame and AnyFrame 54 | // field_anyframe: anyframe = undefined, 55 | }; 56 | 57 | const Struct2 = struct { 58 | field_s3: Struct3 = .{}, 59 | field_slice_s3: []const Struct3 = &.{ .{}, .{} }, 60 | }; 61 | 62 | const Struct3 = struct { 63 | field_i32: i32 = 33, 64 | }; 65 | 66 | const Struct4 = struct {}; 67 | 68 | const ErrorSet1 = error{ 69 | Error1, 70 | Error2, 71 | }; 72 | 73 | const EnumSet1 = enum { 74 | Enum1, 75 | Enum2, 76 | }; 77 | 78 | const EnumSet2 = enum(i32) { 79 | Enum3 = -999, 80 | Enum4 = 999, 81 | }; 82 | 83 | const Union1 = union { 84 | int: i32, 85 | float: f32, 86 | bool: bool, 87 | }; 88 | 89 | const TaggedUnion1 = union(enum) { 90 | int: i32, 91 | float: f32, 92 | bool: bool, 93 | }; 94 | 95 | fn functionOne() void {} 96 | 97 | test "struct with all types" { 98 | const struct1: Struct1 = .{}; 99 | try formatter.format(struct1, .{ .name = "struct1" }); 100 | } 101 | -------------------------------------------------------------------------------- /examples/examples.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const utils = @import("./utils.zig"); 3 | const formatter = @import("tree-fmt").defaultFormatter(); 4 | 5 | test { 6 | std.testing.refAllDeclsRecursive(@import("./struct_with_all_types.zig")); 7 | } 8 | 9 | test "empty strings" { 10 | const empty_slice: []const u8 = ""; 11 | try formatter.format(empty_slice, .{}); 12 | 13 | const empty_sentinel: [*:0]const u8 = ""; 14 | try formatter.format(empty_sentinel, .{}); 15 | } 16 | 17 | test "runtime and comptime slice" { 18 | const ct_slice1 = @typeInfo(struct { f1: u16, f2: u8, f3: u8, f4: u8 }).@"struct".fields; 19 | const ct_slice2 = @typeInfo(struct { f1: u16, f2: u8, f3: u8, f4: u8, f5: u8 }).@"struct".fields; 20 | const ct_slice3 = @typeInfo(struct { f1: u16, f2: u8, f3: u8, f4: u8, f5: u8, f6: u8 }).@"struct".fields; 21 | const ct_slice4 = @typeInfo(struct {}).@"struct".fields; 22 | try formatter.format(ct_slice1, .{ .name = "ct_slice1" }); 23 | try formatter.format(ct_slice2, .{ .name = "ct_slice2" }); 24 | try formatter.format(ct_slice3, .{ .name = "ct_slice3" }); 25 | try formatter.format(ct_slice4, .{ .name = "ct_slice4" }); 26 | 27 | // runtime slice 28 | const arr1 = [_]u8{ 49, 50, 51, 52, 53, 54 }; 29 | const arr2 = [_]u8{ 49, 50, 51, 52, 53 }; 30 | const arr3 = [_]u8{ 49, 50, 51, 52 }; 31 | const arr4 = [_]u8{}; 32 | try formatter.format(arr1, .{ .name = "rt_slice1" }); 33 | try formatter.format(arr2, .{ .name = "rt_slice2" }); 34 | try formatter.format(arr3, .{ .name = "rt_slice3" }); 35 | try formatter.format(arr4, .{ .name = "rt_slice4" }); 36 | } 37 | 38 | test "tagged union" { 39 | const MyUnion = union(enum) { 40 | a: u8, 41 | b: u16, 42 | c: u32, 43 | }; 44 | const my_union: MyUnion = .{ .b = 123 }; 45 | try formatter.format(my_union, .{ .name = "my_union" }); 46 | } 47 | 48 | test "anon struct 2" { 49 | const s = .{ 50 | .a = "hello", 51 | .b = @as(i32, 5), 52 | .c = 3, 53 | .d = 3.14, 54 | }; 55 | try formatter.format(s, .{}); 56 | } 57 | 58 | test "array 123" { 59 | var array: [128]u8 = undefined; 60 | for (&array, 0..) |*e, i| { 61 | e.* = @intCast(i); 62 | } 63 | try formatter.format(array, .{ 64 | .name = "array", 65 | .array_elem_limit = 5, 66 | .print_u8_chars = false, 67 | }); 68 | try formatter.format(@typeInfo(@TypeOf(array)), .{ 69 | .name = "array type info", 70 | }); 71 | 72 | const comp_array = [_]u8{ 1, 2, 3, 4, 5, 6 }; 73 | try formatter.format(comp_array, .{ 74 | .name = "comptime array", 75 | }); 76 | try formatter.format(@typeInfo(@TypeOf(comp_array)), .{ 77 | .name = "array type info", 78 | }); 79 | } 80 | 81 | test "map" { 82 | var map = std.AutoHashMap(u8, u8).init(std.testing.allocator); 83 | defer map.deinit(); 84 | 85 | var i: u8 = 0; 86 | while (i < 3) : (i += 1) { 87 | try map.put(i, i * 2); 88 | } 89 | 90 | try formatter.format(map, .{ .name = "map" }); 91 | try formatter.format(@typeInfo(@TypeOf(map)), .{ .name = "map typeInfo" }); 92 | } 93 | 94 | test "array list slice" { 95 | var array_list = std.ArrayList(u8).init(std.testing.allocator); 96 | defer array_list.deinit(); 97 | 98 | var i: u8 = 0; 99 | while (i < 100) : (i += 1) { 100 | try array_list.append(i); 101 | } 102 | 103 | try formatter.format(array_list, .{ .name = "array" }); 104 | try formatter.format(array_list.items[0..0], .{ 105 | .name = "slice1", 106 | .array_elem_limit = 1, 107 | .print_u8_chars = false, 108 | }); 109 | try formatter.format(array_list.items[0..1], .{ 110 | .name = "slice2", 111 | .array_elem_limit = 1, 112 | .print_u8_chars = false, 113 | }); 114 | try formatter.format(array_list.items[0..2], .{ 115 | .name = "slice3", 116 | .array_elem_limit = 1, 117 | .print_u8_chars = false, 118 | }); 119 | } 120 | 121 | test "array list" { 122 | var array_list = std.ArrayList(u8).init(std.testing.allocator); 123 | defer array_list.deinit(); 124 | 125 | var i: u8 = 0; 126 | while (i < 100) : (i += 1) { 127 | try array_list.append(i); 128 | } 129 | 130 | try formatter.format(array_list, .{ 131 | .array_elem_limit = 5, 132 | .print_u8_chars = false, 133 | }); 134 | try formatter.format(@typeInfo(@TypeOf(array_list)), .{ 135 | .name = "arraylist type info", 136 | }); 137 | } 138 | 139 | test "error union" { 140 | const MyStruct = struct { 141 | a: []const u8 = "hi", 142 | b: u32 = 987, 143 | }; 144 | 145 | const my_struct_value = MyStruct{ .a = "hello", .b = 123 }; 146 | const my_struct_error = error.HiYouGotError; 147 | 148 | try formatter.format(my_struct_error, .{ .name = "my_struct_error" }); 149 | try formatter.format(my_struct_value, .{ .name = "my_struct_value" }); 150 | try formatter.format(@typeInfo(anyerror!MyStruct), .{ .name = "err union type info" }); 151 | } 152 | 153 | test "sentinel ptr" { 154 | const sentinel_array: [*:0]const u8 = "hello world"; 155 | try formatter.format(sentinel_array, .{ .name = "sentinel_array" }); 156 | try formatter.format(@typeInfo([*:0]const u8), .{ .name = "sentinel ptr type info" }); 157 | } 158 | 159 | const LinkedNode = struct { 160 | val: i32, 161 | next: ?*LinkedNode = null, 162 | }; 163 | test "recursive struct" { 164 | var n1 = LinkedNode{ .val = 1 }; 165 | var n2 = LinkedNode{ .val = 2 }; 166 | n1.next = &n2; 167 | n2.next = &n1; 168 | try formatter.format(n1, .{ 169 | .name = "n1", 170 | .ptr_repeat_limit = 2, 171 | }); 172 | try formatter.format(@typeInfo(?*LinkedNode), .{ 173 | .name = "optional ptr type info", 174 | }); 175 | } 176 | 177 | test "multi array list" { 178 | const Car = struct { 179 | license_plate_no: u64 = 555, 180 | }; 181 | const Person = struct { 182 | id: u64, 183 | age: u8, 184 | car: Car = .{}, 185 | }; 186 | 187 | var multi_array_list: std.MultiArrayList(Person) = .{}; 188 | defer multi_array_list.deinit(std.testing.allocator); 189 | 190 | comptime var i: u8 = 0; 191 | inline while (i < 7) : (i += 1) { 192 | try multi_array_list.append(std.testing.allocator, .{ 193 | .id = i, 194 | .age = i, 195 | }); 196 | } 197 | 198 | try formatter.format(multi_array_list, .{ 199 | .name = "multi_array_list1", 200 | .multi_array_list_get_limit = 4, 201 | .print_u8_chars = false, 202 | }); 203 | 204 | try formatter.format(@typeInfo(std.MultiArrayList(Person)), .{ 205 | .name = "multi_array_list type info", 206 | }); 207 | } 208 | 209 | test "zig program" { 210 | const zig_code = 211 | \\ const std = @import("std"); 212 | \\ 213 | \\ pub fn main() void { 214 | \\ std.debug.print("hello {s}", .{"world"}); 215 | \\ } 216 | ; 217 | var ast = try std.zig.Ast.parse(std.testing.allocator, zig_code, .zig); 218 | defer ast.deinit(std.testing.allocator); 219 | try formatter.format(ast.tokens, .{ .name = "ast" }); 220 | try formatter.format(@typeInfo(@TypeOf(ast.tokens)), .{ .name = "ast type info" }); 221 | } 222 | 223 | test "std" { 224 | try formatter.format(@typeInfo(std), .{ 225 | .name = "std type info", 226 | .slice_elem_limit = 1000, 227 | .ignore_u8_in_lists = true, 228 | }); 229 | try formatter.format(@typeInfo(std.net), .{ 230 | .name = "std type info", 231 | .slice_elem_limit = 1000, 232 | .ignore_u8_in_lists = true, 233 | }); 234 | try formatter.format(@typeInfo(std.net.Stream), .{ 235 | .name = "std type info", 236 | .slice_elem_limit = 1000, 237 | .ignore_u8_in_lists = true, 238 | }); 239 | } 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tree Formattar for Zig 2 | - Pretty prints out Zig Values for your debugging needs. 3 | - If you faces any issue with formatting, kindly open an issue. 4 | 5 | ## Versioning 6 | - Current `main` branch tracks zig latest version 7 | - If you need a stable version, see release tags 8 | 9 | ## Objective 10 | - Provide a colored tree-like visual representation of a Zig value to aid in debugging. 11 | 12 | ## Features 13 | - Colored output to distinguish between types and values 14 | - Indentation to show the structure of the value 15 | - Special formatters for following types (Do file a PR or FR if you think there are more types that can require special formatting) 16 | - `std.MultiArrayList` 17 | - `std.HashMapUnmanaged` 18 | 19 | ## Screenshot 20 | ![Screenshot](./images/screenshot.png) 21 | 22 | ## Example 23 | - You can run on of the examples in the `examples` by executing the following command: 24 | ```bash 25 | zig build test -Dtest-filter="anon struct 1" 26 | ``` 27 | - You might need to require to remove `zig-cache` to run it again without changes. 28 | 29 | ## Usage 30 | - Zig Package Manager Example: https://github.com/speed2exe/tree-fmt-example 31 | - `zig.build.zon` 32 | ```zon 33 | .{ 34 | .name = "your_package_name", 35 | .version = "0.0.1", 36 | .dependencies = .{ 37 | .tree_fmt = .{ 38 | // c6398b225f15cdbe35b3951920f634ffd1c65c12 is just commit hash 39 | .url = "https://github.com/speed2exe/tree-fmt/archive/c6398b225f15cdbe35b3951920f634ffd1c65c12.tar.gz", 40 | // just do `zig build`, get the error code and replace with expected hash 41 | .hash = "12201dceb9a9c2c9a6fc83105a7f408132b9ab69173b266e7df2af2c1dd6f814cd51", 42 | }, 43 | }, 44 | .paths = .{ "" }, 45 | } 46 | ``` 47 | - `build.zig` 48 | ```zig 49 | pub fn build(b: *std.Build) void { 50 | // ... 51 | const dep = b.dependency("tree_fmt", .{}); 52 | const tree_fmt = dep.module("tree-fmt"); 53 | your_program.root_module.addImport("tree-fmt", tree_fmt); 54 | } 55 | ``` 56 | 57 | ### Quick Setup 58 | - Fastest and easiest way to if you want to save time and effort. 59 | - This example is in `example_default_tree_formatter.zig` 60 | 61 | ```zig 62 | var tree_formatter = @import("tree-fmt").defaultFormatter(); 63 | 64 | pub fn main() !void { 65 | const my_struct = .{ 1, 2.4, "hi" }; 66 | try tree_formatter.format(my_struct, .{ 67 | .name = "my_struct", // (optional) just an identifier to the root of the tree 68 | }); 69 | } 70 | ``` 71 | - Output: 72 | ``` 73 | some_anon_struct: tuple{comptime comptime_int = 1, comptime comptime_float = 2.4, comptime *const [2:0]u8 = "hi"} 74 | ├─.0: comptime_int => 1 75 | ├─.1: comptime_float => 2.4e+00 76 | └─.2: *const [2:0]u8 @21d169 77 | └─.*: [2:0]u8 hi 78 | ├─[0]: u8 => 104 79 | └─[1]: u8 => 105 80 | ``` 81 | 82 | 83 | ### Proper Setup 84 | - This is recommended, as it gives you more control over writer, allocator and settings. 85 | 86 | ```zig 87 | const std = @import("std"); 88 | 89 | // add imports here 90 | const treeFormatter = @import("tree-fmt").treeFormatter; 91 | 92 | pub fn main() !void { 93 | // initialize your allocator 94 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 95 | const allocator = gpa.allocator(); 96 | defer { 97 | const leaked = gpa.deinit(); 98 | if (leaked) { 99 | @panic("leaked memory!"); 100 | } 101 | } 102 | 103 | // initialize a writer (std.io.Writer) 104 | // tips if you print a lot: wrap with std.io.BufferedWriter to improve performance 105 | var w = std.io.getStdOut().writer(); 106 | 107 | // initialize TreeFormatter with allocator and writer 108 | var tree_formatter = treeFormatter(allocator, w); 109 | 110 | // initialize your value 111 | var sentinel_array: [*:0]const u8 = "hello world"; 112 | 113 | // call the method with writer and value 114 | try tree_formatter.formatValueWithId(sentinel_array, .{ 115 | // .name = "sentinel_array", <-- example setting 116 | // you can find settings at @import("./src/tree_fmt.zig").TreeFormatterSettings; 117 | // you can also leave it blank to use default settings 118 | }); 119 | } 120 | ``` 121 | 122 | - Output: 123 | ``` 124 | sentinel_array: [*:0]const u8 @20a71e "hello world" 125 | ├─[0]: u8 => 104 126 | ├─[1]: u8 => 101 127 | ├─[2]: u8 => 108 128 | ├─[3]: u8 => 108 129 | ├─[4]: u8 => 111 130 | ├─[5]: u8 => 32 131 | ├─[6]: u8 => 119 132 | ├─[7]: u8 => 111 133 | ├─[8]: u8 => 114 134 | ├─[9]: u8 => 108 135 | └─... (showed first 10 out of 11 items only) 136 | ``` 137 | 138 | - You can find other examples in the `examples` directory. To run specific example(s): 139 | ```bash 140 | zig build test -Dtest-filter="name of test" 141 | # e.g. zig build test -Dtest-filter="anon struct 1" 142 | ``` 143 | 144 | ## Example 145 | - `std.ArrayList(u8)` 146 | ``` 147 | .: array_list.ArrayListAligned(u8,null) 148 | ├─.items: []u8 @7efcc912f000 149 | │ ├─[0]: u8 => 0 150 | │ ├─[1]: u8 => 1 151 | │ ├─[2]: u8 => 2 152 | │ ├─[3]: u8 => 3 153 | │ ├─[4]: u8 => 4 154 | │ └─... (showed first 5 out of 100 items only) 155 | ├─.capacity: usize => 105 156 | └─.allocator: mem.Allocator 157 | ├─.ptr: *anyopaque @7fffadc5b3d8 158 | └─.vtable: *const mem.Allocator.VTable @202a38 159 | └─.*: mem.Allocator.VTable 160 | ├─.alloc: *const fn(*anyopaque, usize, u8, usize) ?[*]u8 @238e00 161 | ├─.resize: *const fn(*anyopaque, []u8, u8, usize, usize) bool @2393c0 162 | └─.free: *const fn(*anyopaque, []u8, u8, usize) void @23a2d0 163 | ``` 164 | 165 | - `std.AutoHashMap(u8, u8)` 166 | ``` 167 | map: hash_map.HashMap(u8,u8,hash_map.AutoContext(u8),80) 168 | ├─.unmanaged: hash_map.HashMapUnmanaged(u8,u8,hash_map.AutoContext(u8),80) 169 | │ ├─.iterator() 170 | │ │ ├─.next(): hash_map.HashMapUnmanaged(u8,u8,hash_map.AutoContext(u8),80).Entry 171 | │ │ │ ├─.key_ptr: *u8 @7fcad47f5021 172 | │ │ │ │ └─.*: u8 => 1 173 | │ │ │ └─.value_ptr: *u8 @7fcad47f5029 174 | │ │ │ └─.*: u8 => 2 175 | │ │ ├─.next(): hash_map.HashMapUnmanaged(u8,u8,hash_map.AutoContext(u8),80).Entry 176 | │ │ │ ├─.key_ptr: *u8 @7fcad47f5022 177 | │ │ │ │ └─.*: u8 => 0 178 | │ │ │ └─.value_ptr: *u8 @7fcad47f502a 179 | │ │ │ └─.*: u8 => 0 180 | │ │ └─.next(): hash_map.HashMapUnmanaged(u8,u8,hash_map.AutoContext(u8),80).Entry 181 | │ │ ├─.key_ptr: *u8 @7fcad47f5026 182 | │ │ │ └─.*: u8 => 2 183 | │ │ └─.value_ptr: *u8 @7fcad47f502e 184 | │ │ └─.*: u8 => 4 185 | │ ├─.metadata: ?[*]hash_map.HashMapUnmanaged(u8,u8,hash_map.AutoContext(u8),80).Metadata 186 | │ │ └─.?: [*]hash_map.HashMapUnmanaged(u8,u8,hash_map.AutoContext(u8),80).Metadata @7fcad47f5018 187 | │ ├─.size: u32 => 3 188 | │ └─.available: u32 => 3 189 | ├─.allocator: mem.Allocator 190 | │ ├─.ptr: *anyopaque @7ffc3b6baca0 191 | │ └─.vtable: *const mem.Allocator.VTable @2045b8 192 | │ └─.*: mem.Allocator.VTable 193 | │ ├─.alloc: *const fn(*anyopaque, usize, u8, usize) ?[*]u8 @2433a0 194 | │ ├─.resize: *const fn(*anyopaque, []u8, u8, usize, usize) bool @243960 195 | │ └─.free: *const fn(*anyopaque, []u8, u8, usize) void @244870 196 | └─.ctx: hash_map.AutoContext(u8) => .{} 197 | ``` 198 | 199 | - `std.MultiArrayList...` (see `example_multi_array_list.zig`) 200 | ``` 201 | multi_array_list: multi_array_list.MultiArrayList(example_multi_array_list.Person) 202 | ├─.slice(): multi_array_list.MultiArrayList(example_multi_array_list.Person).Slice 203 | │ └─.items 204 | │ ├─(.id): []u64 @7f8cf20c3000 205 | │ │ ├─[0]: u64 => 0 206 | │ │ ├─[1]: u64 => 1 207 | │ │ ├─[2]: u64 => 2 208 | │ │ ├─[3]: u64 => 3 209 | │ │ ├─[4]: u64 => 4 210 | │ │ └─... (showed first 5 out of 7 items only) 211 | │ ├─(.age): []u8 @7f8cf20c3080 212 | │ │ ├─[0]: u8 => 0 213 | │ │ ├─[1]: u8 => 1 214 | │ │ ├─[2]: u8 => 2 215 | │ │ ├─[3]: u8 => 3 216 | │ │ ├─[4]: u8 => 4 217 | │ │ └─... (showed first 5 out of 7 items only) 218 | │ └─(.car): []example_multi_array_list.Car @7f8cf20c3040 219 | │ ├─[0]: example_multi_array_list.Car 220 | │ │ └─.license_plate_no: u64 => 555 221 | │ ├─[1]: example_multi_array_list.Car 222 | │ │ └─.license_plate_no: u64 => 555 223 | │ ├─[2]: example_multi_array_list.Car 224 | │ │ └─.license_plate_no: u64 => 555 225 | │ ├─[3]: example_multi_array_list.Car 226 | │ │ └─.license_plate_no: u64 => 555 227 | │ ├─[4]: example_multi_array_list.Car 228 | │ │ └─.license_plate_no: u64 => 555 229 | │ └─... (showed first 5 out of 7 items only) 230 | ├─.get 231 | │ ├─(0): example_multi_array_list.Person 232 | │ │ ├─.id: u64 => 0 233 | │ │ ├─.age: u8 => 0 234 | │ │ └─.car: example_multi_array_list.Car 235 | │ │ └─.license_plate_no: u64 => 555 236 | │ ├─(1): example_multi_array_list.Person 237 | │ │ ├─.id: u64 => 1 238 | │ │ ├─.age: u8 => 1 239 | │ │ └─.car: example_multi_array_list.Car 240 | │ │ └─.license_plate_no: u64 => 555 241 | │ ├─(2): example_multi_array_list.Person 242 | │ │ ├─.id: u64 => 2 243 | │ │ ├─.age: u8 => 2 244 | │ │ └─.car: example_multi_array_list.Car 245 | │ │ └─.license_plate_no: u64 => 555 246 | │ ├─(3): example_multi_array_list.Person 247 | │ │ ├─.id: u64 => 3 248 | │ │ ├─.age: u8 => 3 249 | │ │ └─.car: example_multi_array_list.Car 250 | │ │ └─.license_plate_no: u64 => 555 251 | │ └─... (showed first 4 out of 7 items only) 252 | ├─.bytes: [*]align(8) u8 @7f8cf20c3000 253 | ├─.len: usize => 7 254 | └─.capacity: usize => 8 255 | ``` 256 | 257 | - `multi_array_list.MultiArrayList(zig.Ast.TokenList__struct_4206).Slice` 258 | ``` 259 | ast: multi_array_list.MultiArrayList(zig.Ast.TokenList__struct_4200).Slice 260 | ├─.toMultiArrayList(): multi_array_list.MultiArrayList(zig.Ast.TokenList__struct_4200) 261 | │ ├─.slice(): multi_array_list.MultiArrayList(zig.Ast.TokenList__struct_4200).Slice 262 | │ │ └─.items 263 | │ │ ├─(.tag): []zig.tokenizer.Token.Tag @7ff3c81f7098 264 | │ │ │ ├─[0]: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.keyword_const (86) 265 | │ │ │ ├─[1]: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.identifier (2) 266 | │ │ │ ├─[2]: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.equal (12) 267 | │ │ │ ├─[3]: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.builtin (7) 268 | │ │ │ ├─[4]: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.l_paren (16) 269 | │ │ │ └─... (showed first 5 out of 31 items only) 270 | │ │ └─(.start): []u32 @7ff3c81f7000 271 | │ │ ├─[0]: u32 => 1 272 | │ │ ├─[1]: u32 => 7 273 | │ │ ├─[2]: u32 => 11 274 | │ │ ├─[3]: u32 => 13 275 | │ │ ├─[4]: u32 => 20 276 | │ │ └─... (showed first 5 out of 31 items only) 277 | │ ├─.get 278 | │ │ ├─(0): zig.Ast.TokenList__struct_4200 279 | │ │ │ ├─.tag: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.keyword_const (86) 280 | │ │ │ └─.start: u32 => 1 281 | │ │ ├─(1): zig.Ast.TokenList__struct_4200 282 | │ │ │ ├─.tag: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.identifier (2) 283 | │ │ │ └─.start: u32 => 7 284 | │ │ ├─(2): zig.Ast.TokenList__struct_4200 285 | │ │ │ ├─.tag: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.equal (12) 286 | │ │ │ └─.start: u32 => 11 287 | │ │ ├─(3): zig.Ast.TokenList__struct_4200 288 | │ │ │ ├─.tag: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.builtin (7) 289 | │ │ │ └─.start: u32 => 13 290 | │ │ ├─(4): zig.Ast.TokenList__struct_4200 291 | │ │ │ ├─.tag: zig.tokenizer.Token.Tag => zig.tokenizer.Token.Tag.l_paren (16) 292 | │ │ │ └─.start: u32 => 20 293 | │ │ └─... (showed first 5 out of 31 items only) 294 | │ ├─.bytes: [*]align(4) u8 @7ff3c81f7000 295 | │ ├─.len: usize => 31 296 | │ └─.capacity: usize => 38 297 | ├─.ptrs: [2][*]u8 298 | │ ├─[0]: [*]u8 @7ff3c81f7098 299 | │ └─[1]: [*]u8 @7ff3c81f7000 300 | ├─.len: usize => 31 301 | └─.capacity: usize => 38 302 | ``` 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/tree-formatter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = std.builtin; 3 | 4 | const TreeFormatterSettings = @import("./tree-fmt.zig").TreeFormatterSettings; 5 | 6 | const ansi_esc_code = @import("./ansi-esc-code.zig"); 7 | const Color = ansi_esc_code.Color; 8 | const comptimeFmtInColor = ansi_esc_code.comptimeFmtInColor; 9 | const comptimeInColor = ansi_esc_code.comptimeInColor; 10 | 11 | pub fn treeFormatter( 12 | allocator: std.mem.Allocator, 13 | std_io_writer: anytype, 14 | ) TreeFormatter(@TypeOf(std_io_writer)) { 15 | return .{ 16 | .allocator = allocator, 17 | .writer = std_io_writer, 18 | }; 19 | } 20 | 21 | pub fn TreeFormatter(comptime StdIoWriter: type) type { 22 | return struct { 23 | const Self = @This(); 24 | 25 | const arrow = comptimeFmtInColor(Color.bright_black, "=>", .{}); 26 | const empty = " " ++ arrow ++ " .{}"; 27 | const address_fmt = comptimeInColor(Color.blue, "@{x}"); 28 | const not_shown = comptimeInColor(Color.bright_black, " (not shown)"); 29 | const indent_blank = " "; 30 | const indent_bar = "│ "; 31 | const ptr_repeat_reached = comptimeInColor(Color.bright_black, " ...(Repeat Limit Reached)"); 32 | const ptr_repeat = comptimeInColor(Color.bright_black, " (Repeated)"); 33 | const pointer_dereference = comptimeInColor(Color.yellow, ".*"); 34 | const optional_unwrap = comptimeInColor(Color.yellow, ".?"); 35 | const slice_method = comptimeInColor(Color.green, ".slice()"); 36 | const items_method = comptimeInColor(Color.green, ".items()"); 37 | const get_method = comptimeInColor(Color.green, ".get()"); 38 | const iterator_method = comptimeInColor(Color.green, ".iterator()"); 39 | const next_method = comptimeInColor(Color.green, ".next()"); 40 | const ChildPrefix = enum { 41 | non_last, 42 | last, 43 | fn bytes(self: ChildPrefix) []const u8 { 44 | return switch (self) { 45 | .non_last => "├─", 46 | .last => "└─", 47 | }; 48 | } 49 | }; 50 | 51 | /// type to keep count of number of times address of the same value appear 52 | const CountsByAddress = std.AutoHashMap(usize, usize); 53 | 54 | allocator: std.mem.Allocator, 55 | writer: StdIoWriter, 56 | 57 | pub fn init( 58 | allocator: std.mem.Allocator, 59 | writer: StdIoWriter, 60 | settings: TreeFormatterSettings, 61 | ) TreeFormatter { 62 | return TreeFormatter{ 63 | .allocator = allocator, 64 | .settings = settings, 65 | .writer = writer, 66 | }; 67 | } 68 | 69 | pub fn format(self: Self, arg: anytype, settings: TreeFormatterSettings) !void { 70 | var prefix = std.ArrayList(u8).init(self.allocator); 71 | defer prefix.deinit(); 72 | var counts_by_address = CountsByAddress.init(self.allocator); 73 | defer counts_by_address.deinit(); 74 | var instance = Instance{ 75 | .prefix = &prefix, 76 | .counts_by_address = &counts_by_address, 77 | .settings = settings, 78 | .writer = self.writer, 79 | }; 80 | try self.writer.print(comptimeInColor(Color.yellow, "{s}"), .{settings.name}); 81 | try instance.formatRecursive(arg); 82 | try self.writer.print("\n", .{}); 83 | } 84 | 85 | const Instance = struct { 86 | prefix: *std.ArrayList(u8), 87 | counts_by_address: *CountsByAddress, 88 | settings: TreeFormatterSettings, 89 | writer: StdIoWriter, 90 | 91 | fn formatRecursive(self: *Instance, arg: anytype) anyerror!void { // TODO: remove anyerror 92 | try self.formatTypeName(arg); 93 | 94 | const arg_type = @TypeOf(arg); 95 | switch (@typeInfo(arg_type)) { 96 | .@"struct" => |s| { 97 | if (s.fields.len == 0) return try self.writer.writeAll(empty); 98 | 99 | // extra method formatting for some types 100 | if (self.settings.format_multi_array_list and isMultiArrayList(arg_type)) { 101 | try self.formatMultiArrayListMethods(arg, arg_type); 102 | } else if (self.settings.format_multi_array_list and isMultiArrayListSlice(arg_type)) { 103 | try self.formatMultiArrayListSliceMethods(arg); 104 | } else if (self.settings.format_hash_map_unmanaged and isHashMapUnmanaged(arg_type)) { 105 | try self.formatHashMapUnmanagedMethods(arg); 106 | } 107 | 108 | return try self.formatFieldValues(arg, s); 109 | }, 110 | .array => |a| { 111 | if (a.child == u8 and self.settings.print_u8_chars) { 112 | try self.writer.print(" \"{s}\"", .{arg}); 113 | if (self.settings.ignore_u8_in_lists) return; 114 | } 115 | if (a.len == 0) return try self.writer.writeAll(empty); 116 | return try self.formatArrayValues(arg); 117 | }, 118 | .vector => |v| { 119 | if (v.child == u8 and self.settings.print_u8_chars) { 120 | try self.writer.print(" \"{s}\"", .{arg}); 121 | if (self.settings.ignore_u8_in_lists) return; 122 | } 123 | if (v.len == 0) return try self.writer.writeAll(empty); 124 | return try self.formatVectorValues(arg, v); 125 | }, 126 | .pointer => |p| { 127 | // TODO: detect invalid pointers and print them as such 128 | switch (p.size) { 129 | .one => { 130 | const addr: usize = @intFromPtr(arg); 131 | try self.writer.print(" " ++ address_fmt, .{addr}); 132 | 133 | if (self.counts_by_address.getPtr(addr)) |counts_ptr| { 134 | if (counts_ptr.* >= self.settings.ptr_repeat_limit) 135 | return try self.writer.writeAll(ptr_repeat_reached); 136 | try self.writer.writeAll(ptr_repeat); 137 | counts_ptr.* += 1; 138 | } else { 139 | try self.counts_by_address.put(addr, 1); 140 | } 141 | 142 | // TODO: segment ignores unprintable values, more verification is needed 143 | if (addr == 0) return; 144 | if (p.child == anyopaque) return; 145 | const child_type_info = @typeInfo(p.child); 146 | switch (child_type_info) { 147 | .@"fn" => return, 148 | else => {}, 149 | } 150 | if (!isComptime(arg)) { 151 | switch (child_type_info) { 152 | .@"opaque" => return, 153 | else => {}, 154 | } 155 | } 156 | 157 | try self.writeChildComptime(.last, pointer_dereference); 158 | try self.formatValueRecursiveIndented(.last, arg.*); 159 | }, 160 | .slice => { 161 | if (!isComptime(arg)) { 162 | try self.writer.print(" " ++ address_fmt, .{@intFromPtr(arg.ptr)}); 163 | } 164 | if (p.child == u8 and self.settings.print_u8_chars) { 165 | try self.writer.print(" \"{s}\"", .{arg}); 166 | if (self.settings.ignore_u8_in_lists) return; 167 | } 168 | try self.formatSliceValues(arg); 169 | }, 170 | .many, .c => { 171 | try self.writer.print(" " ++ address_fmt, .{@intFromPtr(arg)}); 172 | _ = p.sentinel() orelse return; // if it doesn't have sentinel, it is not possible to safely print the values, so return 173 | if (p.child == u8 and self.settings.print_u8_chars) { 174 | try self.writer.print(" \"{s}\"", .{arg}); 175 | if (self.settings.ignore_u8_in_lists) return; 176 | } 177 | const spanned = std.mem.span(arg); 178 | try self.formatSliceValues(spanned); 179 | }, 180 | } 181 | }, 182 | .optional => { 183 | if (arg) |value| { 184 | try self.writeChildComptime(.last, optional_unwrap); 185 | try self.formatValueRecursiveIndented(.last, value); 186 | } else { 187 | try self.writer.print(" {s} null", .{arrow}); 188 | } 189 | }, 190 | .@"union" => |u| { 191 | if (u.fields.len == 0) return try self.writer.writeAll(empty); 192 | if (u.tag_type) |_| { 193 | return try self.formatTaggedUnion(arg, u, @intFromEnum(arg)); 194 | } 195 | try self.formatFieldValues(arg, u); 196 | }, 197 | .error_union => { 198 | const value = arg catch |err| { 199 | return try self.writer.print(" {s} {any}", .{ arrow, err }); 200 | }; 201 | try self.formatValueRecursiveIndented(.last, value); 202 | }, 203 | .@"enum" => try self.writer.print(" {s} {} ({d})", .{ arrow, arg, @intFromEnum(arg) }), 204 | .@"fn" => { 205 | // cant print any values for fn types 206 | }, 207 | else => try self.writer.print(" {s} {any}", .{ arrow, arg }), 208 | } 209 | } 210 | 211 | fn formatValueRecursiveIndented(self: *Instance, child_prefix: ChildPrefix, arg: anytype) !void { 212 | const backup_len = self.prefix.items.len; 213 | defer self.prefix.shrinkRetainingCapacity(backup_len); 214 | 215 | switch (child_prefix) { 216 | .non_last => try self.prefix.appendSlice(indent_bar), 217 | .last => try self.prefix.appendSlice(indent_blank), 218 | } 219 | try self.formatRecursive(arg); 220 | } 221 | 222 | fn writeIndexingLimitMessage(self: *Instance, limit: usize, len: usize) !void { 223 | try self.writeChild( 224 | .last, 225 | "..." ++ comptimeInColor(Color.bright_black, " (showed first {d} out of {d} items only)"), 226 | .{ limit, len }, 227 | ); 228 | } 229 | 230 | fn formatArrayValues(self: *Instance, array: anytype) !void { 231 | // std.debug.print("array len: {d}\n", .{array.len}); 232 | if (self.settings.array_elem_limit == 0) { 233 | return try self.writeIndexingLimitMessage(self.settings.array_elem_limit, array.len); 234 | } 235 | 236 | if (array.len > self.settings.array_elem_limit) { 237 | inline for (array, 0..) |item, index| { 238 | if (index >= self.settings.array_elem_limit) break; 239 | try self.formatIndexedValueComptime(.non_last, item, index); 240 | } 241 | return try self.writeIndexingLimitMessage(self.settings.array_elem_limit, array.len); 242 | } 243 | 244 | inline for (array[0 .. array.len - 1], 0..) |item, index| { 245 | try self.formatIndexedValueComptime(.non_last, item, index); 246 | } 247 | try self.formatIndexedValueComptime(.last, array[array.len - 1], array.len - 1); 248 | } 249 | 250 | inline fn formatIndexedValueComptime(self: *Instance, child_prefix: ChildPrefix, item: anytype, comptime index: usize) !void { 251 | try self.writeChildComptime(child_prefix, comptimeFmtInColor(Color.yellow, "[{d}]", .{index})); 252 | try self.formatValueRecursiveIndented(child_prefix, item); 253 | } 254 | 255 | fn formatVectorValues(self: *Instance, vector: anytype, vector_type: anytype) !void { 256 | if (self.settings.vector_elem_limit == 0) { 257 | return try self.writeIndexingLimitMessage(self.settings.vector_elem_limit, vector_type.len); 258 | } 259 | 260 | if (vector_type.len > self.settings.vector_elem_limit) { 261 | comptime var i: usize = 0; 262 | inline while (i < vector_type.len) : (i += 1) { 263 | if (i >= self.settings.vector_elem_limit) break; 264 | try self.formatIndexedValueComptime(.non_last, vector[i], i); 265 | } 266 | return try self.writeIndexingLimitMessage(self.settings.vector_elem_limit, vector_type.len); 267 | } 268 | 269 | comptime var i: usize = 0; 270 | inline while (i < vector_type.len - 1) : (i += 1) { 271 | try self.formatIndexedValueComptime(.non_last, vector[i], i); 272 | } 273 | try self.formatIndexedValueComptime(.last, vector[i], i); 274 | } 275 | 276 | fn formatIndexedValue(self: *Instance, comptime child_prefix: ChildPrefix, item: anytype, index: usize) !void { 277 | try self.writeChild(child_prefix, comptimeInColor(Color.yellow, "[{d}]"), .{index}); 278 | try self.formatValueRecursiveIndented(child_prefix, item); 279 | } 280 | 281 | fn formatSliceValuesComptime(self: *Instance, slice: anytype) !void { 282 | inline for (slice, 0..) |item, index| { 283 | if (index == self.settings.slice_elem_limit) { 284 | return try self.writeIndexingLimitMessage(self.settings.slice_elem_limit, slice.len); 285 | } 286 | if (index == slice.len - 1) { 287 | try self.formatIndexedValueComptime(.last, item, index); 288 | } else { 289 | try self.formatIndexedValueComptime(.non_last, item, index); 290 | } 291 | } 292 | } 293 | 294 | fn formatSliceValues(self: *Instance, slice: anytype) !void { 295 | if (slice.len == 0) return self.writer.writeAll(empty); 296 | 297 | if (self.settings.slice_elem_limit == 0) { 298 | return try self.writeIndexingLimitMessage(self.settings.slice_elem_limit, slice.len); 299 | } 300 | 301 | if (isComptime(slice)) { 302 | return try self.formatSliceValuesComptime(slice); 303 | } 304 | 305 | if (slice.len > self.settings.slice_elem_limit) { 306 | for (slice[0..self.settings.slice_elem_limit], 0..) |item, index| { 307 | try self.formatIndexedValue(.non_last, item, index); 308 | } 309 | return try self.writeIndexingLimitMessage(self.settings.slice_elem_limit, slice.len); 310 | } 311 | 312 | const last_index = slice.len - 1; 313 | for (slice[0..last_index], 0..) |item, index| { 314 | try self.formatIndexedValue(.non_last, item, index); 315 | } 316 | try self.formatIndexedValue(.last, slice[last_index], last_index); 317 | } 318 | 319 | fn formatFieldValues(self: *Instance, arg: anytype, arg_type: anytype) !void { 320 | // Note: 321 | // This is set so that unions can be printed for all its values 322 | // This can be removed if we are able to determine the active union 323 | // field during ReleaseSafe and Debug builds, 324 | @setRuntimeSafety(false); 325 | 326 | inline for (arg_type.fields, 0..) |field, index| { 327 | const child_prefix = if (index == arg_type.fields.len - 1) .last else .non_last; 328 | try self.writeChildComptime(child_prefix, comptimeInColor(Color.yellow, "." ++ field.name)); 329 | try self.formatValueRecursiveIndented(child_prefix, @field(arg, field.name)); 330 | } 331 | } 332 | 333 | fn formatTaggedUnionComptime(self: *Instance, arg: anytype) !void { 334 | const tag_name = @tagName(arg); 335 | try self.writeChildComptime(.last, comptimeInColor(Color.yellow, "." ++ tag_name)); 336 | return try self.formatValueRecursiveIndented(.last, @field(arg, tag_name)); 337 | } 338 | 339 | fn formatTaggedUnion(self: *Instance, arg: anytype, arg_type: anytype, target_index: usize) !void { 340 | if (isComptime(arg)) { 341 | return try self.formatTaggedUnionComptime(arg); 342 | } 343 | 344 | inline for (arg_type.fields, 0..) |field, index| { 345 | if (index == target_index) { 346 | try self.writeChildComptime(.last, comptimeInColor(Color.yellow, "." ++ field.name)); 347 | return try self.formatValueRecursiveIndented(.last, @field(arg, field.name)); 348 | } 349 | } 350 | } 351 | 352 | fn formatMultiArrayListMethods(self: *Instance, arr: anytype, comptime arr_type: type) !void { 353 | try self.formatMultiArrayListSliceItems(arr, arr_type); 354 | try self.formatMultiArrayListGet(arr); 355 | } 356 | 357 | fn formatMultiArrayListSliceItems(self: *Instance, arr: anytype, comptime arr_type: type) !void { 358 | const slice = arr.slice(); 359 | try self.writeChildComptime(.non_last, slice_method); 360 | try self.formatTypeName(slice); 361 | 362 | const backup_len = self.prefix.items.len; 363 | defer self.prefix.shrinkRetainingCapacity(backup_len); 364 | try self.prefix.appendSlice(indent_bar); 365 | 366 | try self.writeChildComptime(.last, items_method); 367 | const fields = @typeInfo(arr_type.Field).@"enum".fields; 368 | inline for (fields, 0..) |field, index| { 369 | const backup_len2 = self.prefix.items.len; 370 | defer self.prefix.shrinkRetainingCapacity(backup_len2); 371 | try self.prefix.appendSlice(indent_blank); 372 | 373 | const child_prefix = if (index == fields.len - 1) .last else .non_last; 374 | try self.writeChild(child_prefix, comptimeInColor(Color.green, "(.{s})"), .{field.name}); 375 | const items = slice.items(@as(arr_type.Field, @enumFromInt(index))); 376 | try self.formatValueRecursiveIndented(child_prefix, items); 377 | } 378 | } 379 | 380 | fn formatMultiArrayListGet(self: *Instance, arr: anytype) !void { 381 | try self.writeChildComptime(.non_last, get_method); 382 | 383 | const backup_len = self.prefix.items.len; 384 | var index: usize = 0; 385 | while (index < arr.len) : (index += 1) { 386 | defer self.prefix.shrinkRetainingCapacity(backup_len); 387 | try self.prefix.appendSlice(indent_bar); 388 | 389 | if (index == self.settings.multi_array_list_get_limit) { 390 | return try self.writeIndexingLimitMessage(self.settings.multi_array_list_get_limit, arr.len); 391 | } 392 | if (index == arr.len - 1) { 393 | try self.writeChild(.last, comptimeInColor(Color.green, "({d})"), .{index}); 394 | return try self.formatValueRecursiveIndented(.last, arr.get(index)); 395 | } 396 | try self.writeChild(.non_last, comptimeInColor(Color.green, "({d})"), .{index}); 397 | try self.formatValueRecursiveIndented(.non_last, arr.get(index)); 398 | } 399 | } 400 | 401 | fn formatMultiArrayListSliceMethods(self: *Instance, slice: anytype) !void { 402 | // can reuse the methods from the multi array list 403 | // contains the same logic anyway 404 | const arr = slice.toMultiArrayList(); 405 | try self.formatMultiArrayListMethods(arr, @TypeOf(arr)); 406 | } 407 | 408 | fn formatHashMapUnmanagedMethods(self: *Instance, map: anytype) !void { 409 | try self.writeChildComptime(.non_last, iterator_method); 410 | 411 | var count: usize = 0; 412 | var it = map.iterator(); 413 | const backup_len = self.prefix.items.len; 414 | while (it.next()) |entry| : (count += 1) { 415 | defer self.prefix.shrinkRetainingCapacity(backup_len); 416 | try self.prefix.appendSlice(indent_bar); 417 | 418 | if (count > self.settings.hash_map_entry_limit) { 419 | return try self.writeIndexingLimitMessage(self.settings.hash_map_entry_limit, map.count()); 420 | } 421 | if (count == map.count() - 1) { 422 | try self.writeChildComptime(.last, next_method); 423 | return try self.formatValueRecursiveIndented(.last, entry); 424 | } 425 | try self.writeChildComptime(.non_last, next_method); 426 | try self.formatValueRecursiveIndented(.non_last, entry); 427 | } 428 | } 429 | 430 | fn writeChild(self: *Instance, comptime child_prefix: ChildPrefix, comptime fmt: []const u8, args: anytype) !void { 431 | try self.writer.print("\n{s}" ++ child_prefix.bytes(), .{self.prefix.items}); 432 | try self.writer.print(fmt, args); 433 | } 434 | 435 | fn writeChildComptime(self: *Instance, comptime child_prefix: ChildPrefix, comptime bytes: []const u8) !void { 436 | try self.writer.print("\n{s}" ++ child_prefix.bytes() ++ bytes, .{self.prefix.items}); 437 | } 438 | 439 | fn formatTypeName(self: *Instance, arg: anytype) !void { 440 | try self.writer.print("{s}{s}", .{ 441 | comptimeInColor(Color.bright_black, ": "), 442 | comptimeInColor(Color.cyan, @typeName(@TypeOf(arg))), 443 | }); 444 | } 445 | }; 446 | }; 447 | } 448 | 449 | inline fn isComptime(val: anytype) bool { 450 | return @typeInfo(@TypeOf(.{val})).@"struct".fields[0].is_comptime; 451 | } 452 | 453 | inline fn isMultiArrayList(comptime T: type) bool { 454 | return std.mem.containsAtLeast(u8, @typeName(T), 1, "MultiArrayList") and @hasDecl(T, "Field") and @hasDecl(T, "Slice"); 455 | } 456 | 457 | inline fn isMultiArrayListSlice(comptime T: type) bool { 458 | return std.mem.containsAtLeast(u8, @typeName(T), 1, "MultiArrayList") and @hasDecl(T, "toMultiArrayList") and @hasDecl(T, "items"); 459 | } 460 | 461 | inline fn isHashMapUnmanaged(comptime T: type) bool { 462 | return std.mem.containsAtLeast(u8, @typeName(T), 1, "HashMapUnmanaged") and @hasDecl(T, "KV") and @hasDecl(T, "Hash"); 463 | } 464 | --------------------------------------------------------------------------------