1 /** Various debug printing tools for debug printing in `pure nothrow @safe @nogc` code.
2  *
3  * Copyright: Per Nordlöw 2022-.
4  * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
5  * Authors: $(WEB Per Nordlöw)
6  *
7  * See_Also: https://forum.dlang.org/post/svjjawiezudnugdyriig@forum.dlang.org
8  */
9 module nxt.debugio;
10 
11 public import nxt.stdio : Format;
12 
13 @safe pure:
14 
15 /** Debug print `args` followed by a newline.
16  *
17  * See_Also: https://forum.dlang.org/post/ypxsqtddxvdxunsoluas@forum.dlang.org
18  * See_Also: https://dlang.org/changelog/2.079.0.html#default_after_variadic
19  *
20  * See_Also: https://doc.rust-lang.org/std/macro.dbg.html
21  * See_Also: https://blog.rust-lang.org/2019/01/17/Rust-1.32.0.html#the-dbg-macro
22  * See_Also: https://forum.dlang.org/post/svjjawiezudnugdyriig@forum.dlang.org
23  * See_Also: http://forum.dlang.org/thread/yczwqrbkxdiqijtiynrh@forum.dlang.org?page=1
24  */
25 void dbg(Args...)(scope auto ref Args args, in string file = __FILE_FULL_PATH__, in uint line = __LINE__) {
26 	import nxt.stdio : pwriteln;
27 	debug pwriteln(Format.debugging, file, "(", line, "):", " Debug: ", args, "");
28 	debug _fflush(); // most often what we want before a potentially crash happens
29 }
30 
31 void pdbg(Args...)(in Format fmt, scope auto ref Args args, in string file = __FILE_FULL_PATH__, in uint line = __LINE__) {
32 	import nxt.stdio : pwriteln;
33 	debug pwriteln(fmt, file, "(", line, "):", " Debug: ", args, "");
34 	debug _fflush(); // most often what we want before a potentially crash happens
35 }
36 
37 void dbgf(Args...)(scope Args args, const string file = __FILE_FULL_PATH__, in uint line = __LINE__, const string fun = __FUNCTION__) {
38 	import nxt.stdio : pwriteln;
39 	debug pwriteln(Format.debugging, file, "(", line, "): ", fun, ": Debug: ", args, "");
40 	debug _fflush(); // most often what we want before a potentially crash happens
41 }
42 
43 private void _fflush() @trusted {
44 	import core.stdc.stdio : stdout, fflush;
45 	debug fflush(stdout);
46 }
47 
48 ///
49 pure nothrow @safe @nogc unittest {
50 	static assert(__traits(compiles, { dbg(); })); // ok for dln to discard function qualifiers
51 }
52 
53 enum DumpFormat {
54 	dmd,
55 	rust,
56 	/+ TODO: gcc, +/
57 }
58 
59 /** Debug dump arguments `args` to standard error (`stderr`).
60  *
61  * See_Also: https://forum.dlang.org/post/myxzyfgtcewixwbhvalp@forum.dlang.org
62  */
63 template dump(args...) {
64 	enum fmt = DumpFormat.dmd;
65 	static if (fmt == DumpFormat.dmd)
66 		static immutable header = "%s(%s,1): Debug: [%s]: ";
67 	else static if (fmt == DumpFormat.rust)
68 		static immutable header = "[%s:%s (%s)] ";
69 	import std.traits : isBuiltinType, isAggregateType, FieldNameTuple, isSomeString, isSomeChar;
70 	private void dump(string file = __FILE__, uint line = __LINE__, string fun = __FUNCTION__) {
71 		debug static foreach (arg; args) {{
72 			alias Arg = typeof(arg);
73 			static if (isBuiltinType!(Arg)) {
74 				static immutable isString = isSomeString!Arg;
75 				static immutable isChar = isSomeChar!Arg;
76 				static immutable wrap = isString ? `"` : isChar ? `'` : ``;
77 				stderr.writefln(header ~ "%s: %s%s%s [%s]",
78 								file, line, fun,
79 								__traits(identifier, arg),
80 								wrap, arg, wrap,
81 								Arg.stringof);
82 			}
83 			else static if (isAggregateType!(Arg)) {
84 				/+ TODO: alias this? +/
85 				stderr.writefln(header ~ "%s: { %s } [%s]",
86 								file, line, fun,
87 								__traits(identifier, arg),
88 								toDbgString(arg),
89 								Arg.stringof);
90 			}
91 		}}
92 	}
93 	private string toDbgString(Arg)(Arg o) {
94 		string result;
95 		import std.format;
96 		static foreach (f; FieldNameTuple!(typeof(o))) {
97 			{
98 				alias Member = typeof(__traits(getMember, o, f));
99 				static if (isBuiltinType!(Member))
100 					result ~= format("%s:%s [%s], ",
101 									 f, __traits(getMember, o, f),
102 									 Member.stringof); /+ TODO: avoid ~ +/
103 				else static if (isAggregateType!(Member))
104 					result ~= format("%s = %s [%s], ",
105 									 f, toDbgString(__traits(getMember, o, f)),
106 									 Member.stringof); // TOOD: avoid ~
107 			}
108 		}
109 		return result[0..$-2];
110 	}
111 }
112 
113 ///
114 version (none)
115 pure @safe unittest {
116 	struct Bar { auto c = 'c';}
117 	struct Foo { int s = 2; bool b = false; Bar bar;}
118 	class FooBar { int t; Foo f; }
119 
120 	int i;
121 	float f = 3.14;
122 	char c = 'D';
123 	string s = "some string";
124 	Foo foo;
125 	Bar bar;
126 
127 	dump!(i, c, f, s, foo, 1+3, foo, bar); /+ TODO: don’t print variable name for `1+3` +/
128 }