1 /** Pretty Printing.
2  *
3  * Test: dmd -version=show -preview=dip1000 -preview=in -vcolumns -I.. -i -unittest -version=show -main -run prettyio.d
4  *
5  * TODO: Print structures only matching a certain value passed as template
6  *       parameter. Used in print debugging.
7  */
8 module nxt.prettyio;
9 
10 /++ Format of pretty printing.
11  +/
12 struct Format {
13 	/++ Indentation character.
14      +/
15 	string indentation = "\t";
16 
17 	/++ New line terminator.
18 		See: https://en.wikipedia.org/wiki/Newline
19      +/
20 	string newline = "\n";
21 
22 	/++ Flags that (field) types should be shown.
23      +/
24 	bool showType = false;
25 
26 	/++ Flags that `.init` values should be hidden.
27      +/
28 	bool hideInit = false;
29 }
30 
31 /++ Pretty print `arg`.
32  +/
33 void cwritePretty(T)(T arg, in size_t depth = 0, in char[] name = [], in Format fmt = Format.init) {
34 	scope const(void)*[] ptrs;
35 	cwritePretty_!(T)(arg, depth, name, fmt, ptrs);
36 }
37 
38 private void cwritePretty_(T)(T arg, in size_t depth = 0, in char[] name = [], in Format fmt = Format.init, ref scope const(void)*[] ptrs) {
39 	import std.traits : isArray, isSomeString, isSomeChar, isPointer, hasMember;
40 	import std.stdio : wr = write;
41 
42 	void doIndent() {
43 		foreach (_; 0 .. depth) wr(fmt.indentation);
44 	}
45 
46 	void doFieldName() {
47 		if (name) wr(name, ": ");
48 	}
49 
50 	void doTypeName() {
51 		if (fmt.showType) wr(T.stringof, " ");
52 	}
53 
54 	void doAddress(in void* ptr) {
55 		wr('@', ptr);
56 	}
57 
58 	static if (is(T == struct) || is(T == class) || is(T == union)) {
59 		import std.traits : FieldNameTuple;
60 		void doMembers() {
61 			foreach (memberName; FieldNameTuple!T)
62 				cwritePretty_(__traits(getMember, arg, memberName), depth + 1,
63 								   memberName, fmt, ptrs);
64 		}
65 		void doAggregate() {
66 			/* TODO: Using class.toString requires special care here because
67 			   Object.toString defaults to its qualified type name: */
68 			static if ((is(T == struct) || is(T == union)) && hasMember!(T, "toString")) {
69 				const str = arg.toString;
70 				if (str !is null)
71 					wr('"', str, '"', fmt.newline);
72 				else
73 					wr("[]", fmt.newline); /+ TODO: use cwriteMembers(); instead +/
74 			} else {
75 				if (fmt.hideInit && arg is arg.init) {
76 					wr(".init", fmt.newline);
77 				} else {
78 					wr("{", fmt.newline);
79 					doMembers();
80 					doIndent();
81 					wr("}", fmt.newline);
82 				}
83 			}
84 		}
85 	}
86 
87 	doIndent();
88 	doFieldName();
89 	doTypeName();
90 
91 	static if (is(T == class)) {
92 		const(void)* ptr;
93 		() @trusted { ptr = cast(void*)arg; }();
94 		doAddress(ptr);
95 		if (arg is null) {
96 			wr(fmt.newline);
97 		} else {
98 			const ix = ptrs.indexOf(ptr);
99 			wr(' ');
100 			if (ix != -1) { // `ptr` already printed
101 				wr("#", ix, fmt.newline); // Emacs-Lisp-style back-reference
102 			} else {
103 				ptrs ~= ptr;
104 				wr("#", ptrs.length, ' '); // Emacs-Lisp-style back-reference
105 				doAggregate();
106 			}
107 		}
108 	} else static if (is(T == union) || is(T == struct)) {
109 		doAggregate();
110     } else static if (isPointer!T) {
111 		const ptr = cast(void*)arg;
112 		doAddress(ptr);
113 		if (arg is null) {
114 			wr(fmt.newline);
115 		} else {
116 			const ix = ptrs.indexOf(ptr);
117 			wr(' ');
118 			if (ix != -1) { // `ptr` already printed
119 				wr("#", ix, fmt.newline); // Emacs-Lisp-style back-reference
120 			} else {
121 				wr("#", ptrs.length, " -> "); // Emacs-Lisp-style back-reference
122 				ptrs ~= ptr;
123 				static if (is(immutable typeof(*arg))) { // `arg` is not `void*`
124 					cwritePretty_(*arg, depth, [], fmt, ptrs);
125 				}
126 			}
127 		}
128     } else static if (isSomeString!T) {
129 		if (arg !is null)
130 			wr('"', arg, '"', fmt.newline);
131 		else
132 			wr("[]", fmt.newline);
133     } else static if (isSomeChar!T) {
134         wr(`'`, arg, `'`, fmt.newline);
135     } else static if (isArray!T) {
136         wr("[");
137 		if (arg.length) { // non-empty
138 			import std.range.primitives : ElementType;
139 			alias E = ElementType!(T);
140 			enum scalarE = __traits(isScalar, E);
141 			static if (!scalarE)
142 				wr(fmt.newline);
143 			foreach (const i, ref element; arg) {
144 				static if (scalarE) {
145 					if (i != 0)
146 						wr(',');
147 					cwritePretty_(element, 0, [],
148 									   Format([], [], false, false), ptrs);
149 				} else {
150 					cwritePretty_(element, depth + 1, [],
151 									   fmt, ptrs);
152 				}
153 			}
154 			static if (!scalarE)
155 				doIndent();
156 		}
157 		wr("]", fmt.newline);
158     } else static if (__traits(isAssociativeArray, T)) {
159         wr(arg, fmt.newline);
160     } else {
161         wr(arg, fmt.newline);
162     }
163 }
164 
165 /++ Colorized version of `std.stdio.write`.
166  +/
167 void cwrite(T...)(T args) {
168 	import std.stdio : sw = write;
169 	version (none) import nxt.ansi_escape : putWithSGRs; /+ TODO: use below: +/
170 	// alias w = pathWrapperSymbol;
171 	static foreach (arg; args) {{
172 		static immutable S = typeof(arg).stringof;
173 		// pragma(msg, __FILE__, "(", __LINE__, ",1): Debug: ", S);
174 		// static if (S == "URL")
175 		// 	sw(w, arg, w); /+ TODO: SGR.yellowForegroundColor +/
176 		// else static if (S == "Path")
177 		// 	sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/
178 		// else static if (S == "FilePath")
179 		// 	sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/
180 		// else static if (S == "DirPath")
181 		// 	sw(w, arg, w); /+ TODO: SGR.cyanForegroundColor +/
182 		// else static if (S == "ExePath")
183 		// 	sw(w, arg, w); /+ TODO: SGR.lightRedForegroundColor +/
184 		// else static if (S == "FileName")
185 		// 	sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/
186 		// else static if (S == "DirName")
187 		// 	sw(w, arg, w); /+ TODO: SGR.redForegroundColor +/
188 		// else static if (S == "ExitStatus")
189 		// 	sw(w, arg, w); /+ TODO: SGR.greenForegroundColor if arg == 0, otherwise SGR.redForegroundColor +/
190 		// else
191 		sw(arg);
192 	}}
193 }
194 
195 /++ Wrapper symbol when printing paths and URLs to standard out (`stdout`) and standard error (`stderr`).
196  +/
197 private static immutable string pathWrapperSymbol = `"`;
198 
199 /++ Colorized version of `std.stdio.writeln`.
200  +/
201 void cwriteln(T...)(T args) {
202 	import std.stdio : swln = writeln;
203 	cwrite!(T)(args);
204 	swln();
205 }
206 
207 version (show)
208 unittest {
209 	@safe struct P {
210 		double x;
211 		double y;
212 		double z;
213 	}
214 
215 	@safe union U {
216 		void* ptr;
217 		size_t word;
218 	}
219 
220 	@safe struct S {
221 		int x;
222 		double y;
223 		char[3] c3 = "abc";
224 		wchar[3] wc3 = "abc";
225 		dchar[3] dc3 = "abc";
226 		string w3 = "xyz";
227 		wstring ws3 = "xyz";
228 		dstring ds3 = "xyz";
229 		string ns = []; // null string
230 		string s = ""; // non-null empty string
231 		int[3] i3;
232 		float[3] f3;
233 		double[3] d3;
234 		real[3] r3;
235 		int[string] ais = ["a":1, "b":2];
236 		P[string] ps = ["a":P(1,2,3)];
237 		P p0;
238 		P* pp0 = null;
239 		P* pp1 = new P(1,2,3);
240 		U u;
241 	}
242 
243 	@safe class Cls {
244 		this(int x) {
245 			this.x = x;
246 			this.parent = this;
247 		}
248 		int x;
249 		Cls parent;
250 	}
251 
252 	@safe struct Top {
253 		S s;
254 		S[2] s2;
255 		Cls cls;
256 		Cls clsNull;
257 		string name;
258 		int[] numbers;
259 	}
260 
261 	S s = S(10, 20.5);
262     Top top = { s, [s,s], new Cls(1), null, "example", [1, 2, 3] };
263     top.cwritePretty(0, "top", Format("\t", "\n", false, false));
264     top.cwritePretty(0, "top", Format("\t", "\n", false, true));
265     top.cwritePretty(0, "top", Format("\t", "\n", true, false));
266     top.cwritePretty(0, "top", Format("\t", "\n", true, true));
267 }
268 
269 /** Array-specialization of `indexOf` with default predicate.
270  *
271  * TODO: Add optimized implementation for needles with length >=
272  * `largeNeedleLength` with no repeat of elements.
273  */
274 ptrdiff_t indexOf(T)(scope inout(T)[] haystack,
275 					 scope const(T)[] needle) @trusted {
276 	// enum largeNeedleLength = 4;
277 	if (haystack.length < needle.length)
278 		return -1;
279 	foreach (const offset; 0 .. haystack.length - needle.length + 1)
280 		if (haystack.ptr[offset .. offset + needle.length] == needle)
281 			return offset;
282 	return -1;
283 }
284 /// ditto
285 ptrdiff_t indexOf(T)(scope inout(T)[] haystack,
286 					 scope const T needle) {
287 	static if (is(T == char))
288 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
289 	foreach (const offset, const ref element; haystack)
290 		if (element == needle)
291 			return offset;
292 	return -1;
293 }