A pretty printer for zig
zig
at main 658 lines 17 kB view raw
1const std = @import("std"); 2const Io = std.Io; 3const Type = std.builtin.Type; 4 5pub fn pretty(value: anytype) Pretty(@TypeOf(value)) { 6 return Pretty(@TypeOf(value)).init(value); 7} 8 9pub fn prettyO(value: anytype, comptime opts: Options) PrettyWithOptions(@TypeOf(value), opts) { 10 return PrettyWithOptions(@TypeOf(value), opts).init(value); 11} 12 13const root = @import("root"); 14const default_options: Options = if (@hasDecl(root, "pretty_options")) 15 root.pretty_options 16else 17 Options{}; 18 19pub const Options = struct { 20 /// Disable pretty printing. 21 /// 22 /// If enabled, will format values using `{any}` 23 disable: bool = false, 24 25 /// Show type names 26 show_type_names: bool = true, 27 /// Don't show type names for the root value 28 skip_root_type_name: bool = false, 29 30 /// Disable color output 31 no_color: bool = false, 32 /// Customize the theme, change colors or separators 33 theme: Theme = .{}, 34 35 /// Inline print arrays 36 array_inline: bool = false, 37 /// Always show array indices, even in inline mode 38 array_always_show_index: bool = false, 39 40 /// Treat pointers to `[]u8` / `[]const u8` as strings 41 ptr_array_u8_as_string: bool = true, 42 /// Treat pointers to a u8 slice as strings 43 ptr_slice_u8_as_string: bool = true, 44 /// Inline print slices 45 ptr_slice_inline: bool = false, 46 /// Always show slice indices, even in inline mode 47 ptr_slice_always_show_index: bool = false, 48 49 /// Inline print structs 50 struct_inline: bool = false, 51 /// Highlight tag type of unions 52 union_highlight_tag: bool = true, 53}; 54 55pub const Theme = struct { 56 pub const Color = enum { 57 dim, 58 field, 59 value, 60 @"error", 61 type, 62 string, 63 many_ptr, 64 null, 65 true, 66 false, 67 }; 68 69 indent_width: comptime_int = 2, 70 71 field_name_type_sep: []const u8 = ": ", 72 type_value_sep: []const u8 = " = ", 73 index_value_sep: []const u8 = " = ", 74 75 index_open: []const u8 = "[", 76 index_close: []const u8 = "]", 77 78 array_open: []const u8 = "{", 79 array_close: []const u8 = "}", 80 81 vec_open: []const u8 = "(", 82 vec_close: []const u8 = ")", 83 84 color_dim: Io.Terminal.Color = .dim, 85 color_field: Io.Terminal.Color = .green, 86 color_value: Io.Terminal.Color = .blue, 87 color_error: Io.Terminal.Color = .red, 88 color_type: Io.Terminal.Color = .bright_blue, 89 color_string: Io.Terminal.Color = .magenta, 90 color_many_ptr: Io.Terminal.Color = .magenta, 91 color_null: Io.Terminal.Color = .cyan, 92 color_true: Io.Terminal.Color = .bright_green, 93 color_false: Io.Terminal.Color = .bright_red, 94 95 pub inline fn getColor(comptime this: Theme, comptime color: Color) Io.Terminal.Color { 96 return @field(this, "color_" ++ @tagName(color)); 97 } 98}; 99 100pub fn Pretty(comptime T: type) type { 101 return PrettyWithOptions(T, default_options); 102} 103 104pub fn PrettyWithOptions(comptime T: type, comptime options: Options) type { 105 if (default_options.disable) return struct { 106 value: T, 107 108 pub fn init(val: T) @This() { 109 return .{ .value = val }; 110 } 111 112 pub fn format(this: *const @This(), w: *std.Io.Writer) error{WriteFailed}!void { 113 try w.print("{any}", .{this.value}); 114 } 115 }; 116 117 const global = struct { 118 var tty: ?Io.Terminal = null; 119 }; 120 const ctx: Context = Context{ .options = options }; 121 122 return struct { 123 value: T, 124 125 pub fn init(val: T) @This() { 126 return .{ .value = val }; 127 } 128 129 pub fn format(this: *const @This(), w: *std.Io.Writer) error{WriteFailed}!void { 130 if (global.tty == null) { 131 var buffer: [1]u8 = undefined; 132 const stderr = std.debug.lockStderr(&buffer).terminal(); 133 defer std.debug.unlockStderr(); 134 135 global.tty = stderr; 136 global.tty.?.writer = w; 137 } 138 139 var run = Runtime{ 140 .out = w, 141 .tty = global.tty.?, 142 .no_color = options.no_color, 143 }; 144 145 return run.pretty(ctx, this.value, .{}); 146 } 147 }; 148} 149 150pub const Runtime = struct { 151 out: *Io.Writer, 152 tty: Io.Terminal, 153 154 no_color: bool = default_options.no_color, 155 156 pub inline fn print( 157 this: *const Runtime, 158 comptime fmt: []const u8, 159 args: anytype, 160 ) error{WriteFailed}!void { 161 return this.out.print(fmt, args); 162 } 163 164 pub inline fn write(this: *const Runtime, text: []const u8) error{WriteFailed}!void { 165 _ = try this.out.write(text); 166 } 167 168 pub fn pretty( 169 this: *const Runtime, 170 comptime ctx: Context, 171 value: anytype, 172 comptime opts: InnerFmtOptions, 173 ) error{WriteFailed}!void { 174 return innerFmt(@TypeOf(value), ctx, this, value, opts); 175 } 176 177 pub inline fn setColor( 178 this: *const Runtime, 179 comptime ctx: Context, 180 comptime color: Theme.Color, 181 ) void { 182 if (this.no_color or default_options.no_color) return; 183 this.tty.setColor(comptime ctx.options.theme.getColor(color)) catch {}; 184 } 185 186 pub inline fn setColorRaw(this: *const Runtime, color: Io.Terminal.Color) void { 187 if (this.no_color or default_options.no_color) return; 188 this.tty.setColor(color) catch {}; 189 } 190 191 pub inline fn resetColor(this: *const Runtime) void { 192 if (this.no_color or default_options.no_color) return; 193 this.tty.setColor(.reset) catch {}; 194 } 195}; 196 197pub const Context = struct { 198 depth: comptime_int = 0, 199 200 options: Options, 201 exited_comptime: bool = false, 202 203 pub fn inComptime(comptime this: Context) bool { 204 if (!this.exited_comptime) return false; 205 return @inComptime(); 206 } 207}; 208 209pub const InnerFmtOptions = struct { 210 skip_type_name: bool = !default_options.show_type_names, 211 force_inline: bool = false, 212}; 213 214fn innerFmt( 215 comptime T: type, 216 comptime ctx: Context, 217 run: *const Runtime, 218 value: T, 219 comptime opts: InnerFmtOptions, 220) error{WriteFailed}!void { 221 const info = @typeInfo(T); 222 223 if (!opts.skip_type_name) 224 try printType(T, ctx, run, value); 225 226 if (ctx.options.struct_inline or opts.force_inline) { 227 if (std.meta.hasMethod(T, "prettyInline")) { 228 return value.prettyInline(ctx, run); 229 } 230 } else { 231 if (std.meta.hasMethod(T, "pretty")) { 232 return value.pretty(ctx, run); 233 } 234 } 235 236 return switch (info) { 237 .bool => formatBool(ctx, run, value), 238 .null => formatNull(ctx, run), 239 .type => formatType(ctx, run, value), 240 241 // comptime types 242 .comptime_int, 243 .comptime_float, 244 // number types 245 .int, 246 .float, 247 // enum types 248 .@"enum", 249 .enum_literal, 250 => formatValue(ctx, run, value), 251 252 .optional => |opt| formatOptional(opt.child, ctx, run, value), 253 .@"struct" => |st| formatStruct(T, ctx, st, run, value), 254 .@"union" => formatUnion(T, ctx, run, value), 255 256 .error_set => formatErrorSet(ctx, run, value), 257 .error_union => formatErrorUnion(ctx, run, value), 258 259 .array => |arr| formatArray(ctx, arr, run, value), 260 .vector => |vec| formatVector(ctx, vec, run, value), 261 262 .@"fn" => formatFn(T, ctx, run), 263 264 .pointer => |ptr| switch (ptr.size) { 265 .one => formatPtrOne(ctx, ptr, run, value), 266 .slice => formatPtrSlice(ctx, ptr, run, value), 267 .many => formatPtrMany(ctx, ptr, run, value), 268 else => { 269 run.setColor(ctx, .@"error"); 270 try run.print("Unimplemented! ({} = {any})", .{ info, value }); 271 run.resetColor(); 272 }, 273 }, 274 275 else => { 276 run.setColor(ctx, .@"error"); 277 try run.print("Unimplemented! ({} = {any})", .{ info, value }); 278 run.resetColor(); 279 }, 280 }; 281} 282 283inline fn printType( 284 comptime T: type, 285 comptime ctx: Context, 286 run: *const Runtime, 287 value: T, 288) error{WriteFailed}!void { 289 const active_type = comptime std.meta.activeTag(@typeInfo(T)); 290 const excluded = comptime switch (active_type) { 291 .void, .noreturn, .undefined, .null, .@"fn" => true, 292 else => false, 293 }; 294 295 if ((ctx.depth != 0 or !ctx.options.skip_root_type_name) and !excluded) { 296 run.setColor(ctx, .dim); 297 298 if (ctx.options.show_type_names) { 299 try run.write(@typeName(T)); 300 301 if (active_type == .@"union") { 302 if (ctx.options.union_highlight_tag) { 303 run.resetColor(); 304 run.setColor(ctx, .field); 305 } 306 307 try run.print("{}", .{std.meta.activeTag(value)}); 308 309 if (ctx.options.union_highlight_tag) { 310 run.resetColor(); 311 run.setColor(ctx, .dim); 312 } 313 } 314 } 315 try run.write(ctx.options.theme.type_value_sep); 316 317 run.resetColor(); 318 } 319} 320 321inline fn formatBool( 322 comptime ctx: Context, 323 run: *const Runtime, 324 value: bool, 325) !void { 326 if (value) 327 run.setColor(ctx, .true) 328 else 329 run.setColor(ctx, .false); 330 331 try run.print("{}", .{value}); 332 run.resetColor(); 333} 334 335inline fn formatNull(comptime ctx: Context, run: *const Runtime) !void { 336 run.setColor(ctx, .null); 337 try run.write("null"); 338 run.resetColor(); 339} 340 341inline fn formatType( 342 comptime ctx: Context, 343 run: *const Runtime, 344 value: type, 345) !void { 346 run.setColor(ctx, .type); 347 run.setColorRaw(.bold); 348 try run.write(@typeName(value)); 349 run.resetColor(); 350} 351 352inline fn formatValue( 353 comptime ctx: Context, 354 run: *const Runtime, 355 value: anytype, 356) !void { 357 run.setColor(ctx, .value); 358 try run.print("{}", .{value}); 359 run.resetColor(); 360} 361 362inline fn formatOptional( 363 comptime T: type, 364 comptime ctx: Context, 365 run: *const Runtime, 366 value: ?T, 367) !void { 368 return if (value) |val| 369 innerFmt(T, ctx, run, val, .{ .skip_type_name = true }) 370 else 371 formatNull(ctx, run); 372} 373 374inline fn formatStruct( 375 comptime T: type, 376 comptime ctx: Context, 377 comptime st: Type.Struct, 378 run: *const Runtime, 379 value: T, 380) !void { 381 const next_ctx = Context{ 382 .depth = ctx.depth + 1, 383 .exited_comptime = ctx.exited_comptime, 384 .options = ctx.options, 385 }; 386 387 if (ctx.options.struct_inline) { 388 run.setColor(ctx, .dim); 389 try run.write(".{ "); 390 run.resetColor(); 391 } 392 393 comptime var index = 0; 394 inline for (st.fields) |field| { 395 indent(next_ctx, next_ctx.options.struct_inline, run); 396 if (index != 0 and ctx.options.struct_inline) { 397 run.setColor(ctx, .dim); 398 try run.write(", "); 399 run.resetColor(); 400 } 401 402 run.setColor(ctx, .field); 403 try run.write("." ++ field.name ++ 404 ctx.options.theme.field_name_type_sep); 405 run.resetColor(); 406 407 try innerFmt(field.type, next_ctx, run, @field(value, field.name), .{}); 408 409 index += 1; 410 } 411 412 if (ctx.options.struct_inline) { 413 run.setColor(ctx, .dim); 414 try run.write(" }"); 415 run.resetColor(); 416 } 417} 418 419inline fn formatUnion( 420 comptime T: type, 421 comptime ctx: Context, 422 run: *const Runtime, 423 value: T, 424) !void { 425 switch (value) { 426 inline else => |val| try innerFmt(@TypeOf(val), ctx, run, val, .{ .skip_type_name = true }), 427 } 428} 429 430inline fn formatArray( 431 comptime ctx: Context, 432 comptime arr: Type.Array, 433 run: *const Runtime, 434 value: anytype, 435) !void { 436 if (arr.len == 0) { 437 try run.write(ctx.options.theme.array_open); 438 try run.write(ctx.options.theme.array_close); 439 return; 440 } 441 442 const next_ctx = Context{ 443 .depth = ctx.depth + 1, 444 .exited_comptime = ctx.exited_comptime, 445 .options = ctx.options, 446 }; 447 448 if (ctx.options.array_inline) try arrayOpen(ctx, run); 449 450 comptime var index = 0; 451 inline for (value) |val| { 452 indent( 453 next_ctx, 454 next_ctx.options.array_inline, 455 run, 456 ); 457 458 run.setColor(ctx, .dim); 459 if (index != 0 and ctx.options.array_inline) 460 try run.write(", "); 461 462 if (!ctx.options.array_inline or ctx.options.array_always_show_index) { 463 try run.write(ctx.options.theme.index_open); 464 try run.print("{}", .{index}); 465 try run.write(ctx.options.theme.index_close ++ 466 ctx.options.theme.index_value_sep); 467 } 468 469 run.resetColor(); 470 471 try innerFmt(arr.child, next_ctx, run, val, .{ .skip_type_name = true }); 472 473 index += 1; 474 } 475 476 if (ctx.options.array_inline) try arrayClose(ctx, run); 477} 478 479inline fn formatVector( 480 comptime ctx: Context, 481 comptime vec: Type.Vector, 482 run: *const Runtime, 483 value: @Vector(vec.len, vec.child), 484) !void { 485 run.setColor(ctx, .dim); 486 try run.write(ctx.options.theme.vec_open ++ " "); 487 run.resetColor(); 488 489 inline for (0..vec.len) |idx| { 490 if (idx != 0) { 491 run.setColor(ctx, .dim); 492 try run.write(", "); 493 run.resetColor(); 494 } 495 496 try innerFmt(vec.child, ctx, run, value[idx], .{ .skip_type_name = true }); 497 } 498 499 run.setColor(ctx, .dim); 500 try run.write(" " ++ ctx.options.theme.vec_close); 501 run.resetColor(); 502} 503 504inline fn formatPtrOne( 505 comptime ctx: Context, 506 comptime ptr: Type.Pointer, 507 run: *const Runtime, 508 value: anytype, 509) !void { 510 const is_string = switch (@typeInfo(ptr.child)) { 511 .array => |arr| arr.child == u8 and ctx.options.ptr_array_u8_as_string, 512 else => false, 513 }; 514 if (is_string) return formatString(ctx, run, value); 515 516 try innerFmt(ptr.child, ctx, run, value.*, .{ .skip_type_name = true }); 517} 518 519inline fn formatPtrSlice( 520 comptime ctx: Context, 521 comptime ptr: Type.Pointer, 522 run: *const Runtime, 523 value: anytype, 524) !void { 525 if (ptr.child == u8 and ctx.options.ptr_slice_u8_as_string) 526 return formatString(ctx, run, value); 527 528 const next_ctx = Context{ 529 .depth = if (!ctx.options.ptr_slice_inline) ctx.depth + 1 else ctx.depth, 530 .exited_comptime = ctx.exited_comptime, 531 .options = ctx.options, 532 }; 533 534 if (ctx.options.ptr_slice_inline) try arrayOpen(ctx, run); 535 536 var count: usize = 0; 537 for (value, 0..) |val, idx| { 538 count += 1; 539 540 indent( 541 next_ctx, 542 next_ctx.options.ptr_slice_inline, 543 run, 544 ); 545 546 run.setColor(ctx, .dim); 547 if (idx != 0 and ctx.options.ptr_slice_inline) 548 try run.write(", "); 549 550 if (!ctx.options.ptr_slice_inline or ctx.options.ptr_slice_always_show_index) { 551 try run.write(ctx.options.theme.index_open); 552 try run.print("{}", .{idx}); 553 try run.write(ctx.options.theme.index_close ++ 554 ctx.options.theme.index_value_sep); 555 } 556 557 run.resetColor(); 558 559 try innerFmt(ptr.child, next_ctx, run, val, .{ .skip_type_name = true }); 560 } 561 562 if (count == 0) { 563 run.setColor(ctx, .dim); 564 try run.write(ctx.options.theme.array_open); 565 try run.write(ctx.options.theme.array_close); 566 run.resetColor(); 567 } 568 569 if (ctx.options.ptr_slice_inline) try arrayClose(ctx, run); 570} 571 572inline fn formatPtrMany( 573 comptime ctx: Context, 574 comptime ptr: Type.Pointer, 575 run: *const Runtime, 576 value: anytype, 577) !void { 578 _ = ptr; 579 run.setColor(ctx, .dim); 580 run.setColor(ctx, .many_ptr); 581 try run.print("{*}", .{value}); 582 run.resetColor(); 583} 584 585inline fn formatString( 586 comptime ctx: Context, 587 run: *const Runtime, 588 value: anytype, 589) !void { 590 run.setColor(ctx, .string); 591 try run.write("\""); 592 try run.write(value); 593 try run.write("\""); 594 run.resetColor(); 595} 596 597inline fn formatErrorSet( 598 comptime ctx: Context, 599 run: *const Runtime, 600 value: anyerror, 601) !void { 602 run.setColor(ctx, .@"error"); 603 try run.write("error."); 604 try run.write(@errorName(value)); 605 run.resetColor(); 606} 607 608inline fn formatErrorUnion( 609 comptime ctx: Context, 610 run: *const Runtime, 611 value: anytype, 612) !void { 613 const val = value catch |err| return formatErrorSet(ctx, run, err); 614 return innerFmt( 615 @TypeOf(val), 616 ctx, 617 run, 618 val, 619 .{ .skip_type_name = true }, 620 ); 621} 622 623inline fn formatFn( 624 comptime T: type, 625 comptime ctx: Context, 626 run: *const Runtime, 627) !void { 628 run.setColor(ctx, .dim); 629 run.setColor(ctx, .type); 630 run.setColorRaw(.bold); 631 632 try run.write(@typeName(T)); 633 634 run.resetColor(); 635} 636 637inline fn indent( 638 comptime ctx: Context, 639 comptime inline_option: bool, 640 run: *const Runtime, 641) void { 642 if (inline_option) return; 643 644 const text: [ctx.depth * ctx.options.theme.indent_width]u8 = @splat(' '); 645 run.write("\n" ++ text) catch {}; 646} 647 648fn arrayOpen(comptime ctx: Context, run: *const Runtime) !void { 649 run.setColor(ctx, .dim); 650 try run.write(ctx.options.theme.array_open ++ " "); 651 run.resetColor(); 652} 653 654fn arrayClose(comptime ctx: Context, run: *const Runtime) !void { 655 run.setColor(ctx, .dim); 656 try run.write(" " ++ ctx.options.theme.array_close); 657 run.resetColor(); 658}