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 }