1 /** 2 * Debugging tools 3 * 4 * Authors: Tristan Brice Velloza Kildaire (deavmi) 5 */ 6 module niknaks.debugging; 7 8 import std.traits : isArray, ForeachType; 9 import std.conv : to; 10 import std.string : format; 11 import std.stdio : writeln; 12 import niknaks.text : genTabs, genX; 13 14 version(unittest) 15 { 16 import std.stdio : write; 17 } 18 19 /** 20 * Dumps the provided array 21 * 22 * Params: 23 * array = the array to dump 24 */ 25 template dumpArray(alias array) 26 if(isArray!(typeof(array))) 27 { 28 // Get the type of the array 29 private alias symbolType = typeof(array); 30 pragma(msg, "Symboltype: ", symbolType); 31 32 // Get the arrays'e element type 33 private alias elementType = ForeachType!(symbolType); 34 pragma(msg, "Element type: ", elementType); 35 36 // Get the array's name as a string 37 private string ident = __traits(identifier, array); 38 39 /** 40 * Dumps the array within the provided boundries 41 * 42 * Params: 43 * start = beginning index 44 * end = ending index 45 * Returns: the formatted dump text 46 */ 47 public string dumpArray(size_t start, size_t end, size_t depth = 0) 48 { 49 // String out 50 string output; 51 52 for(size_t i = start; i < end; i++) 53 { 54 string textOut; 55 56 static if(isArray!(elementType) && !__traits(isSame, elementType, string)) 57 { 58 textOut = (depth ? "":ident)~"["~to!(string)(i)~"] = ..."; 59 60 // Tab by depth 61 textOut = genTabs(depth)~textOut; 62 63 output ~= textOut~"\n"; 64 65 66 output ~= dumpArray_rec(array[i], 0, array[i].length, depth+1); 67 } 68 else 69 { 70 textOut = (depth ? "":ident)~"["~to!(string)(i)~"] = "~to!(string)(array[i]); 71 72 // Tab by depth 73 textOut = genTabs(depth)~textOut; 74 75 output ~= textOut~"\n"; 76 } 77 } 78 79 return output; 80 } 81 82 /** 83 * Dumps the entire array 84 * 85 * Returns: the formatted dump text 86 */ 87 public string dumpArray() 88 { 89 return dumpArray!(array)(0, array.length); 90 } 91 } 92 93 /** 94 * Test dumping an array of integers 95 */ 96 unittest 97 { 98 int[] test = [1,2,3]; 99 writeln("Should have 3 things (BEGIN)"); 100 write(dumpArray!(test)); 101 writeln("Should have 3 things (END)"); 102 } 103 104 /** 105 * Test dumping an array of integers 106 * with custom bounds 107 */ 108 unittest 109 { 110 int[] test = [1,2,3]; 111 writeln("Should have nothing (BEGIN)"); 112 write(dumpArray!(test)(0, 0)); 113 writeln("Should have nothing (END)"); 114 } 115 116 /** 117 * Test dumping an array of integers 118 * with custom bounds 119 */ 120 unittest 121 { 122 int[] test = [1,2,3]; 123 writeln("Should have 2 (BEGIN)"); 124 write(dumpArray!(test)(1, 2)); 125 writeln("Should have 2 (END)"); 126 } 127 128 /** 129 * Test dumping an array of integer 130 * arrays 131 */ 132 unittest 133 { 134 int[][] test = [ [1,2,3], [4,5,6]]; 135 write(dumpArray!(test)); 136 } 137 138 139 /** 140 * Test dumping an array of an array of 141 * integer arrays 142 */ 143 unittest 144 { 145 int[][][] test = [ 146 [ [1,2], 147 [3,4] 148 ], 149 150 [ 151 [4,5], 152 [6,7] 153 ] 154 ]; 155 write(dumpArray!(test)); 156 } 157 158 /** 159 * Tests out the compile-time component-type 160 * detection of `string` in any array of them 161 */ 162 unittest 163 { 164 string[] stringArray = ["Hello", "world"]; 165 writeln(dumpArray!(stringArray)); 166 } 167 168 /** 169 * Tests the array-name dumping 170 */ 171 unittest 172 { 173 int[] bruh = [1,2,3]; 174 string g = dumpArray!(bruh)(); 175 writeln(g); 176 } 177 178 /** 179 * Dumps a given array within the provided boundries 180 * 181 * This method is used internally for the recursive 182 * call as it won't work with the template. 183 * 184 * Params: 185 * array = the array 186 * start = beginning index 187 * end = ending index 188 * Returns: the formatted dump text 189 */ 190 private string dumpArray_rec(T)(T[] array, size_t start, size_t end, size_t depth = 0) 191 { 192 // String out 193 string output; 194 195 // Obtain the array's name (symbol as-a-string) 196 string ident = __traits(identifier, array); 197 198 for(size_t i = start; i < end; i++) 199 { 200 string textOut; 201 202 static if(isArray!(T) && !__traits(isSame, T, string)) 203 { 204 textOut = (depth ? "":ident)~"["~to!(string)(i)~"] = ..."; 205 206 // Tab by depth 207 textOut = genTabs(depth)~textOut; 208 209 output ~= textOut~"\n"; 210 211 212 output ~= dumpArray_rec(array[i], 0, array[i].length, depth+1); 213 } 214 else 215 { 216 textOut = (depth ? "":ident)~"["~to!(string)(i)~"] = "~to!(string)(array[i]); 217 218 // Tab by depth 219 textOut = genTabs(depth)~textOut; 220 221 output ~= textOut~"\n"; 222 } 223 } 224 225 return output; 226 } 227 228 /** 229 * Proxy function to call 230 * `std.stdio`'s `writeln' 231 * function 232 * 233 * Params: 234 * msg = the message 235 */ 236 private void writerButStringOnly(string msg) 237 { 238 writeln(msg); 239 } 240 241 /** 242 * Base mixin for debugging tooling 243 * 244 * Params: 245 * func = the function's symbol 246 * writer = the callable to use to write 247 * using 248 */ 249 public mixin template FuncDebugBase(alias func, alias writer) 250 { 251 private string functionNameStr = __traits(identifier, func); 252 private alias formalParemeterNames = ParameterIdentifierTuple!(func); 253 private alias arguments = __traits(parameters); 254 255 /** 256 * Enter a function 257 * 258 * Params: 259 * showArguments = `false` by default, this 260 * selects whether or not the formal paremeter 261 * names and argument values should be printed 262 * out following the entry message 263 */ 264 public void enter(bool showArguments = false) 265 { 266 string enterMessage = format("%s(): Enter", functionNameStr); 267 writer(enterMessage); 268 269 if(showArguments) 270 { 271 args(); 272 } 273 } 274 275 /** 276 * Prints the formal parameter names 277 * and their respective argument values 278 * 279 * Params: 280 * spc = the spacer to provide, default 281 * is one tab 282 */ 283 public void args(string spc = genTabs(1)) 284 { 285 static foreach(i; 0..formalParemeterNames.length) 286 { 287 writer(format("%s%s = %s", spc, formalParemeterNames[i], to!(string)(arguments[i]))); 288 } 289 } 290 291 /** 292 * Prints a message from within the 293 * function 294 * 295 * Params: 296 * message = the message 297 */ 298 public void say(string message) 299 { 300 string debugMessage = format("%s(): %s", functionNameStr, message); 301 writer(debugMessage); 302 } 303 304 /** 305 * Leaves a function 306 */ 307 public void leave() 308 { 309 string leaveMessage = format("%s(): Leave", functionNameStr); 310 writer(leaveMessage); 311 } 312 } 313 314 /** 315 * Function debugging mixin 316 * with the given function 317 * and the writer to use 318 * 319 * Params: 320 * func = the function's 321 * symbol 322 * writer = the string-callable 323 * function to use for writing 324 * (by default this is a proxy 325 * to `writeln(string)`) 326 * the messages to 327 */ 328 public mixin template FuncDebug(alias func, void function(string) writer = &writerButStringOnly) 329 { 330 // We must do this because the code gets parameterized 331 // and then dumped and refers to things not in the scope 332 // where it is mixed-in 333 // 334 // TODO: See if we can make this a mixin in-and-of-itself 335 import niknaks.debugging : FuncDebugBase, genTabs, genX; 336 import std.traits : ParameterIdentifierTuple; 337 338 mixin FuncDebugBase!(func, writer); 339 } 340 341 /** 342 * Usage of the `FuncDebug` mixin 343 * template using the default writer 344 * function which writes to standard 345 * output 346 */ 347 unittest 348 { 349 void myFunc(int x, int y) 350 { 351 mixin FuncDebug!(myFunc); 352 enter(true); 353 354 say("Good day governor"); 355 356 leave(); 357 } 358 359 myFunc(69, 420); 360 } 361 362 /** 363 * Function debugging mixin 364 * with the given function 365 * and the writer to use 366 * 367 * Params: 368 * func = the function's 369 * symbol 370 * writer = the string-callable 371 * delegate to use for writing 372 * (by default this is a proxy 373 * to `writeln(string)`) 374 * the messages to 375 */ 376 public mixin template FuncDebug(alias func, void delegate(string) writer = &writerButStringOnly) 377 { 378 // We must do this because the code gets parameterized 379 // and then dumped and refers to things not in the scope 380 // where it is mixed-in 381 // 382 // TODO: See if we can make this a mixin in-and-of-itself 383 import niknaks.debugging : FuncDebugBase, genTabs, genX; 384 import std.traits : ParameterIdentifierTuple; 385 386 mixin FuncDebugBase!(func, writer); 387 } 388 389 /** 390 * Example usage of the `FuncDebug` 391 * mixin template using a custom 392 * writer delegate 393 */ 394 unittest 395 { 396 string[] written; 397 void testWriter(string s) 398 { 399 written ~= s; 400 } 401 402 void myFunc(int x, int y) 403 { 404 mixin FuncDebug!(myFunc, &testWriter); 405 enter(true); 406 407 say("Good day governor"); 408 409 leave(); 410 } 411 412 myFunc(69, 420); 413 414 assert(written[0] == format("%s(): Enter", "myFunc")); 415 assert(written[1] == "\tx = 69"); 416 assert(written[2] == "\ty = 420"); 417 assert(written[3] == format("%s(): %s", "myFunc", "Good day governor")); 418 assert(written[4] == format("%s(): Leave", "myFunc")); 419 }