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 }