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(indentation: [], newLine: [], showType: false, hideInit: false), ptrs); 149 } else { 150 cwritePretty_(element, depth + 1, [], fmt, ptrs); 151 } 152 } 153 static if (!scalarE) 154 doIndent(); 155 } 156 wr("]", fmt.newline); 157 } else static if (__traits(isAssociativeArray, T)) { 158 wr(arg, fmt.newline); 159 } else { 160 wr(arg, fmt.newline); 161 } 162 } 163 164 /++ Colorized version of `std.stdio.write`. 165 +/ 166 void cwrite(T...)(T args) { 167 import std.stdio : sw = write; 168 version (none) import nxt.ansi_escape : putWithSGRs; /+ TODO: use below: +/ 169 // alias w = pathWrapperSymbol; 170 static foreach (arg; args) {{ 171 static immutable S = typeof(arg).stringof; 172 // pragma(msg, __FILE__, "(", __LINE__, ",1): Debug: ", S); 173 // static if (S == "URL") 174 // sw(w, arg, w); /+ TODO: SGR.yellowForegroundColor +/ 175 // else static if (S == "Path") 176 // sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/ 177 // else static if (S == "FilePath") 178 // sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/ 179 // else static if (S == "DirPath") 180 // sw(w, arg, w); /+ TODO: SGR.cyanForegroundColor +/ 181 // else static if (S == "ExePath") 182 // sw(w, arg, w); /+ TODO: SGR.lightRedForegroundColor +/ 183 // else static if (S == "FileName") 184 // sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/ 185 // else static if (S == "DirName") 186 // sw(w, arg, w); /+ TODO: SGR.redForegroundColor +/ 187 // else static if (S == "ExitStatus") 188 // sw(w, arg, w); /+ TODO: SGR.greenForegroundColor if arg == 0, otherwise SGR.redForegroundColor +/ 189 // else 190 sw(arg); 191 }} 192 } 193 194 /++ Wrapper symbol when printing paths and URLs to standard out (`stdout`) and standard error (`stderr`). 195 +/ 196 private static immutable string pathWrapperSymbol = `"`; 197 198 /++ Colorized version of `std.stdio.writeln`. 199 +/ 200 void cwriteln(T...)(T args) { 201 import std.stdio : swln = writeln; 202 cwrite!(T)(args); 203 swln(); 204 } 205 206 version (show) 207 unittest { 208 @safe struct P { 209 double x; 210 double y; 211 double z; 212 } 213 214 @safe union U { 215 void* ptr; 216 size_t word; 217 } 218 219 @safe struct S { 220 int x; 221 double y; 222 char[3] c3 = "abc"; 223 wchar[3] wc3 = "abc"; 224 dchar[3] dc3 = "abc"; 225 string w3 = "xyz"; 226 wstring ws3 = "xyz"; 227 dstring ds3 = "xyz"; 228 string ns = []; // null string 229 string s = ""; // non-null empty string 230 int[3] i3; 231 float[3] f3; 232 double[3] d3; 233 real[3] r3; 234 int[string] ais = ["a":1, "b":2]; 235 P[string] ps = ["a":P(1,2,3)]; 236 P p0; 237 P* pp0 = null; 238 P* pp1 = new P(1,2,3); 239 U u; 240 } 241 242 @safe class Cls { 243 this(int x) { 244 this.x = x; 245 this.parent = this; 246 } 247 int x; 248 Cls parent; 249 } 250 251 @safe struct Top { 252 S s; 253 S[2] s2; 254 Cls cls; 255 Cls clsNull; 256 string name; 257 int[] numbers; 258 } 259 260 S s = S(10, 20.5); 261 Top top = { s, [s,s], new Cls(1), null, "example", [1, 2, 3] }; 262 top.cwritePretty(0, "top", Format(indentation: "\t", newline: "\n", showType: false, hideInit: false)); 263 top.cwritePretty(0, "top", Format(indentation: "\t", newline: "\n", showType: false, hideInit: true)); 264 top.cwritePretty(0, "top", Format(indentation: "\t", newline: "\n", showType: true, hideInit: false)); 265 top.cwritePretty(0, "top", Format(indentation: "\t", newline: "\n", showType: true, hideInit: true)); 266 } 267 268 /** Array-specialization of `indexOf` with default predicate. 269 * 270 * TODO: Add optimized implementation for needles with length >= 271 * `largeNeedleLength` with no repeat of elements. 272 */ 273 ptrdiff_t indexOf(T)(scope inout(T)[] haystack, 274 scope const(T)[] needle) @trusted { 275 // enum largeNeedleLength = 4; 276 if (haystack.length < needle.length) 277 return -1; 278 foreach (const offset; 0 .. haystack.length - needle.length + 1) 279 if (haystack.ptr[offset .. offset + needle.length] == needle) 280 return offset; 281 return -1; 282 } 283 /// ditto 284 ptrdiff_t indexOf(T)(scope inout(T)[] haystack, 285 scope const T needle) { 286 static if (is(T == char)) 287 assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 288 foreach (const offset, const ref element; haystack) 289 if (element == needle) 290 return offset; 291 return -1; 292 }