A SpaceTraders Agent
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
9fn Context(comptime runtime: bool) type {
10 if (runtime) {
11 return struct {
12 indent_width: u8 = 2,
13 indent_level: usize = 0,
14 };
15 } else {
16 return struct {
17 indent_width: comptime_int = 2,
18 indent_level: comptime_int = 0,
19 };
20 }
21}
22
23const Options = struct {
24 type_name: bool = true,
25 last_layer_type_name: bool = true,
26
27 follow_pointers: bool = true,
28};
29
30pub fn Pretty(comptime T: type) type {
31 const global = struct {
32 var conf: ?std.Io.tty.Config = null;
33 };
34 return struct {
35 value: T,
36 pub fn init(val: T) @This() {
37 return .{ .value = val };
38 }
39 pub fn format(this: *const @This(), w: *std.Io.Writer) error{WriteFailed}!void {
40 if (global.conf == null) {
41 global.conf = .detect(.stderr());
42 }
43 return innerFmt(w, global.conf.?, T, this.value, false, .{}, .{});
44 }
45 };
46}
47
48const Color = enum {
49 dim,
50 reset,
51 text,
52 field,
53 value,
54};
55
56fn setColor(
57 w: *Io.Writer,
58 tty: Io.tty.Config,
59 color: Color,
60) void {
61 switch (color) {
62 .dim => {
63 tty.setColor(w, .dim) catch {};
64 },
65 .reset => {
66 tty.setColor(w, .reset) catch {};
67 },
68 .text => {
69 // tty.setColor(w, .italic) catch {};
70 tty.setColor(w, .dim) catch {};
71 tty.setColor(w, .blue) catch {};
72 },
73 .field => {
74 tty.setColor(w, .green) catch {};
75 },
76 .value => {
77 // tty.setColor(w, .italic) catch {};
78 tty.setColor(w, .blue) catch {};
79 },
80 }
81}
82
83fn innerFmt(
84 w: *std.Io.Writer,
85 tty: std.Io.tty.Config,
86 comptime T: type,
87 value: T,
88 comptime runtime: bool,
89 ctx: Context(runtime),
90 opts: Options,
91) error{WriteFailed}!void {
92 const info = @typeInfo(T);
93
94 switch (info) {
95 .bool => try printBool(w, tty, value, opts),
96 .int => |int| try printInt(w, tty, int, value, opts),
97 .float => |float| try printFloat(w, tty, float, value, opts),
98 .@"enum", .enum_literal => try printEnum(w, tty, value, opts),
99 .optional => |optional| try printOptional(w, tty, optional, value, runtime, ctx, opts),
100 .pointer => |ptr| {
101 if (opts.follow_pointers) {
102 try printPointer(w, tty, ptr, value, runtime, ctx, opts);
103 } else {}
104 },
105 .array => |array| try printArray(w, tty, array, value, runtime, ctx, opts),
106 .@"struct" => |str| try printStruct(T, w, tty, str, value, runtime, ctx, opts),
107 else => {
108 tty.setColor(w, .red) catch {};
109 try w.print("Unimplemented! ({})", .{info});
110 setColor(w, tty, .reset);
111 },
112 }
113}
114
115fn printBool(
116 w: *Io.Writer,
117 tty: Io.tty.Config,
118 value: bool,
119 opts: Options,
120) !void {
121 if (opts.type_name) {
122 setColor(w, tty, .dim);
123 try w.print("bool => ", .{});
124 setColor(w, tty, .reset);
125 } else {
126 tty.setColor(w, .dim) catch {};
127 try w.print("=> ", .{});
128 setColor(w, tty, .reset);
129 }
130 setColor(w, tty, .value);
131 if (value) {
132 tty.setColor(w, .bright_green) catch {};
133 } else {
134 tty.setColor(w, .bright_red) catch {};
135 }
136 try w.print("{}", .{value});
137 setColor(w, tty, .reset);
138}
139
140fn printInt(
141 w: *Io.Writer,
142 tty: Io.tty.Config,
143 int: Type.Int,
144 value: anytype,
145 opts: Options,
146) !void {
147 if (opts.type_name) {
148 tty.setColor(w, .dim) catch {};
149 try w.print("{s}{} => ", .{ if (int.signedness == .signed) "i" else "u", int.bits });
150 setColor(w, tty, .reset);
151 } else {
152 tty.setColor(w, .dim) catch {};
153 try w.print("=> ", .{});
154 setColor(w, tty, .reset);
155 }
156 setColor(w, tty, .value);
157 try w.print("{}", .{value});
158 setColor(w, tty, .reset);
159}
160
161fn printFloat(
162 w: *Io.Writer,
163 tty: Io.tty.Config,
164 float: Type.Float,
165 value: anytype,
166 opts: Options,
167) !void {
168 if (opts.type_name) {
169 tty.setColor(w, .dim) catch {};
170 try w.print("f{} => ", .{float.bits});
171 setColor(w, tty, .reset);
172 } else {
173 tty.setColor(w, .dim) catch {};
174 try w.print("=> ", .{});
175 setColor(w, tty, .reset);
176 }
177 setColor(w, tty, .value);
178 try w.print("{}", .{value});
179 setColor(w, tty, .reset);
180}
181
182fn printEnum(
183 w: *Io.Writer,
184 tty: Io.tty.Config,
185 value: anytype,
186 opts: Options,
187) !void {
188 if (opts.type_name) {
189 tty.setColor(w, .dim) catch {};
190 try w.print("{s} => ", .{@typeName(@TypeOf(value))});
191 setColor(w, tty, .reset);
192 } else {
193 tty.setColor(w, .dim) catch {};
194 try w.print("=> ", .{});
195 setColor(w, tty, .reset);
196 }
197 setColor(w, tty, .value);
198 try w.print("{}", .{value});
199 setColor(w, tty, .reset);
200}
201
202fn printOptional(
203 w: *Io.Writer,
204 tty: Io.tty.Config,
205 optional: Type.Optional,
206 value: anytype,
207 comptime runtime: bool,
208 ctx: Context(runtime),
209 opts: Options,
210) !void {
211 var next_opts = opts;
212 next_opts.type_name = if (!opts.type_name and !opts.last_layer_type_name) false else true;
213 next_opts.last_layer_type_name = opts.type_name;
214
215 if (opts.type_name) {
216 tty.setColor(w, .dim) catch {};
217 try w.print("?", .{});
218 setColor(w, tty, .reset);
219 }
220
221 if (value) |val| {
222 try innerFmt(w, tty, optional.child, val, runtime, ctx, next_opts);
223 } else {
224 if (opts.type_name) {
225 tty.setColor(w, .dim) catch {};
226 try w.print("{s} => ", .{@typeName(optional.child)});
227 setColor(w, tty, .reset);
228 } else {
229 tty.setColor(w, .dim) catch {};
230 try w.print("=> ", .{});
231 setColor(w, tty, .reset);
232 }
233
234 tty.setColor(w, .blue) catch {};
235 try w.writeAll("null");
236 setColor(w, tty, .reset);
237 }
238}
239
240fn printArray(
241 w: *Io.Writer,
242 tty: Io.tty.Config,
243 array: Type.Array,
244 value: anytype,
245 comptime runtime: bool,
246 ctx: Context(runtime),
247 opts: Options,
248) !void {
249 comptime var next_ctx = ctx;
250
251 var next_opts = opts;
252 next_opts.type_name = if (!opts.type_name and !opts.last_layer_type_name) false else true;
253 next_opts.last_layer_type_name = opts.type_name;
254
255 if (opts.type_name) {
256 tty.setColor(w, .dim) catch {};
257 try w.print("[{}]{s} =>", .{ array.len, @typeName(array.child) });
258 setColor(w, tty, .reset);
259 } else {
260 tty.setColor(w, .dim) catch {};
261 try w.print("=> ", .{});
262 setColor(w, tty, .reset);
263 }
264
265 inline for (value, 0..) |item, idx| {
266 try indent(w, runtime, ctx);
267
268 setColor(w, tty, .field);
269 try w.print("[{}] ", .{idx});
270 setColor(w, tty, .reset);
271
272 next_ctx.indent_level = ctx.indent_level + 1;
273 next_opts.type_name = false;
274
275 try innerFmt(w, tty, @TypeOf(item), item, runtime, next_ctx, next_opts);
276 }
277}
278
279fn printPointer(
280 w: *Io.Writer,
281 tty: Io.tty.Config,
282 ptr: Type.Pointer,
283 value: anytype,
284 comptime runtime: bool,
285 ctx: Context(runtime),
286 opts: Options,
287) !void {
288 var next_opts = opts;
289 next_opts.type_name = if (!opts.type_name and !opts.last_layer_type_name) false else true;
290 next_opts.last_layer_type_name = opts.type_name;
291
292 switch (ptr.size) {
293 .one => {
294 if (opts.type_name) {
295 tty.setColor(w, .dim) catch {};
296 try w.print("*{s}", .{if (ptr.is_const) "const " else ""});
297 setColor(w, tty, .reset);
298 }
299
300 if (ptr.child == anyopaque or @typeInfo(ptr.child) == .@"fn")
301 return;
302
303 try innerFmt(w, tty, ptr.child, value, runtime, ctx, next_opts);
304 },
305 .slice => {
306 if (opts.type_name) {
307 setColor(w, tty, .dim);
308 try w.print("[]{s}", .{if (ptr.is_const) "const " else ""});
309 setColor(w, tty, .reset);
310 }
311
312 if (ptr.child == u8) {
313 tty.setColor(w, .dim) catch {};
314 try w.print("{s}=>", .{if (opts.type_name) "u8 " else ""});
315 setColor(w, tty, .reset);
316
317 try indent(w, runtime, ctx);
318
319 setColor(w, tty, .text);
320 try w.print("\"{s}\"", .{value});
321 setColor(w, tty, .reset);
322 return;
323 }
324
325 setColor(w, tty, .dim);
326 try w.print("{s}{s}", .{
327 if (opts.type_name) @typeName(ptr.child) else "",
328 if (opts.type_name) " =>" else "=>",
329 });
330 setColor(w, tty, .reset);
331
332 const run_ctx: Context(true) = .{ .indent_width = ctx.indent_width, .indent_level = ctx.indent_level };
333 var next_ctx: Context(true) = .{ .indent_width = ctx.indent_width, .indent_level = ctx.indent_level };
334
335 var count: usize = 0;
336 for (value, 0..) |item, idx| {
337 try indent(w, true, run_ctx);
338
339 setColor(w, tty, .field);
340 try w.print("[{}] ", .{idx});
341 setColor(w, tty, .reset);
342
343 next_ctx.indent_level = ctx.indent_level + 1;
344 next_opts.type_name = false;
345
346 try innerFmt(w, tty, @TypeOf(item), item, true, next_ctx, next_opts);
347 count += 1;
348 }
349
350 if (count == 0) {
351 setColor(w, tty, .dim);
352 try w.writeAll(" empty");
353 setColor(w, tty, .reset);
354 }
355 },
356 else => {
357 try w.print("unimplemented {}", .{ptr});
358 },
359 }
360}
361
362fn printStruct(
363 comptime T: type,
364 w: *Io.Writer,
365 tty: Io.tty.Config,
366 str: Type.Struct,
367 value: anytype,
368 comptime runtime: bool,
369 ctx: Context(runtime),
370 opts: Options,
371) !void {
372 var next_opts = opts;
373 next_opts.type_name = if (!opts.type_name and !opts.last_layer_type_name) false else true;
374 next_opts.last_layer_type_name = opts.type_name;
375
376 setColor(w, tty, .dim);
377 if (opts.type_name) {
378 try w.print("{s} =>", .{@typeName(T)});
379 } else {
380 try w.print("=>", .{});
381 }
382 setColor(w, tty, .reset);
383
384 if (runtime) {
385 var next_ctx: Context(true) = ctx;
386
387 inline for (str.fields) |field| {
388 try indent(w, runtime, ctx);
389
390 setColor(w, tty, .field);
391 try w.print(".{s}", .{field.name});
392 setColor(w, tty, .reset);
393
394 setColor(w, tty, .dim);
395 if (opts.type_name) {
396 try w.writeAll(": ");
397 } else try w.writeByte(' ');
398 setColor(w, tty, .reset);
399
400 next_ctx.indent_level = ctx.indent_level + 1;
401
402 try innerFmt(
403 w,
404 tty,
405 field.type,
406 @field(value, field.name),
407 runtime,
408 next_ctx,
409 next_opts,
410 );
411 }
412 } else {
413 comptime var next_ctx: Context(false) = ctx;
414
415 inline for (str.fields) |field| {
416 try indent(w, runtime, ctx);
417
418 setColor(w, tty, .field);
419 try w.print(".{s}", .{field.name});
420 setColor(w, tty, .reset);
421
422 setColor(w, tty, .dim);
423 if (opts.type_name) {
424 try w.writeAll(": ");
425 } else try w.writeByte(' ');
426 setColor(w, tty, .reset);
427
428 next_ctx.indent_level = ctx.indent_level + 1;
429
430 try innerFmt(
431 w,
432 tty,
433 field.type,
434 @field(value, field.name),
435 runtime,
436 next_ctx,
437 next_opts,
438 );
439 }
440 }
441}
442
443fn indent(w: *Io.Writer, comptime runtime: bool, ctx: Context(runtime)) !void {
444 try w.writeByte('\n');
445 if (runtime) {
446 for (0..((ctx.indent_level + 1) * ctx.indent_width)) |_| {
447 try w.writeByte(' ');
448 }
449 } else {
450 const text: [(ctx.indent_level + 1) * ctx.indent_width]u8 = @splat(' ');
451 try w.writeAll(&text);
452 }
453}