1 module jcli.introspect.groups;
2 
3 // TODO: 
4 // Ways to specify children instead of the parent.
5 // Needs some changes in the graph code. Not hard.
6 //
7 // TODO: 
8 // Ways to parent all commands within a module.
9 // I bet this one can be really useful.
10 // Should also be pretty easy to implement.
11 //
12 // TODO:
13 // Discover commands through a parent command, by looking at its children.
14 // Should be very useful when you decide to go top-down
15 // and have more control over the command layout.
16 // I imagine it shouldn't be that hard?
17 
18 import jcli.core.udas : ParentCommand;
19 
20 import std.traits;
21 import std.meta;
22 
23 template escapedName(T)
24 {
25     import std.string : replace;
26     import std.conv : to;
27     import std.path : baseName;
28 
29     // The idea here is to minimize collisions between type symbols.
30     enum location = __traits(getLocation, T);
31     enum escapedName = (baseName(location[0]) ~ fullyQualifiedName!T)
32             .replace(".", "_")
33             .replace(" ", "_")
34             .replace("!", "_")
35         ~ location[1].to!string;
36 }
37 
38 struct TypeGraphNode
39 {
40     int typeIndex;
41     
42     /// Shows which field of the given command type points to the parent context.
43     /// Is -1 if the related command does not have a member pointing to the parent context. 
44     int fieldIndex;
45 }
46 
47 template TypeGraph(Types...)
48 {
49     alias Node = TypeGraphNode;
50 
51     // Note:
52     // I'm realying on the idea that name lookup in scopes is linear in time to
53     // essentially have a fake compile time AA.
54     // Normal AA's are not allowed to be used at compile time rn. 
55     // (E.g. `immutable int[int] example = [1: 2]` does not compile).
56     private string getGraphMixinText()
57     {
58         size_t[string] typeToIndex;
59         static foreach (index, Type; Types)
60             typeToIndex[escapedName!Type] = index;
61         Node[][Types.length] childrenGraph;
62 
63         foreach (outerIndex, Type; Types)
64         {
65             static foreach (fieldIndex, field; Type.tupleof)
66             {
67                 static if (is(typeof(field) : T*, T))
68                 {
69                     static if (hasUDA!(field, ParentCommand))
70                     {{
71                         const name = escapedName!T;
72                         if (auto index = name in typeToIndex)
73                             childrenGraph[*index] ~= Node(cast(int) outerIndex, cast(int) fieldIndex);
74                         else
75                             assert(false, name ~ " not found among types.");
76                     }}
77                 }
78             }
79         }
80 
81         // TODO: is bit array worth it?
82         bool[Types.length] isNotRootCache;
83         foreach (parentIndex, children; childrenGraph)
84         {
85             foreach (childNode; children)
86                 isNotRootCache[childNode.typeIndex] = true;
87         }
88 
89         {
90             // Check for cicles in the graph.
91             // Cicles will stifle the compiler (but I'm not sure).
92             // TODO: use bit array?
93             bool[Types.length] visited = false;
94 
95             bool isCyclic(size_t index)
96             {
97                 if (visited[index])
98                     return true;
99                 
100                 visited[index] = true;
101                 foreach (childNode; childrenGraph[index])
102                 {
103                     if (isCyclic(childNode.typeIndex))
104                         return true;
105                 }
106                 return false;
107             }
108 
109             foreach (parentIndex, isNotRoot; isNotRootCache)
110             {
111                 if (isNotRoot)
112                     continue;
113                 visited[] = false;
114                 if (isCyclic(parentIndex))
115                 {
116                     // TODO: report more info here.
117                     assert(false, "The command graph contains a cycle");
118                 }
119             }
120         }
121 		
122         import std.array : appender;
123         import std.format : formattedWrite;
124         import std.algorithm : map;
125 
126         auto ret = appender!string;
127 
128         {
129             ret ~= "template Mappings() {";
130             foreach (key, index; typeToIndex)
131                 formattedWrite(ret, "enum int %s = %d;", key, index);
132             ret ~= "}";
133         }
134 
135         {
136             // auto rootTypes = appender!string;
137             // rootTypes ~= "alias RootTypes = AliasSeq!(";
138 
139             auto rootTypeIndices = appender!string;
140             rootTypeIndices ~= "immutable int[] rootTypeIndices = [";
141             {
142                 size_t appendedCount = 0;
143                 foreach (nodeIndex, isNotRoot; isNotRootCache)
144                 {
145                     if (isNotRoot)
146                         continue;
147                     if (appendedCount > 0)
148                     {
149                         rootTypeIndices ~= ", ";
150                         // rootTypes ~= ", ";
151                     }
152                     appendedCount++;
153                     
154                     formattedWrite(rootTypeIndices, "%d", nodeIndex);
155                     // formattedWrite(rootTypes, "Types[%d]", nodeIndex);
156                 }
157             }
158             rootTypeIndices ~= "];\n";
159 
160             ret ~= rootTypeIndices[];
161             ret ~= "\n";
162 
163             // rootTypes ~= ");\n";
164             // ret ~= rootTypes[];
165             // ret ~= "\n";
166         }
167         {
168             formattedWrite(ret, "immutable Node[][] Adjacencies = %s;\n", childrenGraph);
169         }
170 
171         // Extra info, not actually used
172         static if (0)
173         {
174             formattedWrite(ret, "immutable bool[] IsNodeRoot = %s;\n", isNotRootCache[].map!"!a");
175 
176             auto types = appender!string;
177             auto fields = appender!string;
178 
179             foreach (parentIndex, ParentType; Types)
180             {
181                 types ~= "alias " ~ escapedName!ParentType ~ " = AliasSeq!(";
182                 fields ~= "alias " ~ escapedName!ParentType ~ " = AliasSeq!(";
183 
184                 foreach (typeIndexIndex, childNode; childrenGraph[parentIndex])
185                 {
186                     if (typeIndexIndex > 0)
187                     {
188                         types ~= ", ";
189                         fields ~= ", ";
190                     }
191 
192                     formattedWrite(fields, "Types[%d].tupleof[%d]", 
193                         childNode.typeIndex, childNode.fieldIndex);
194 
195                     formattedWrite(types, "Types[%d]", 
196                         childNode.typeIndex);
197                 }
198 
199                 types ~= ");\n";
200                 fields ~= ");\n";
201             }
202 
203             ret ~= "\ntemplate Commands() {\n";
204             ret ~= types[];
205             ret ~= "\n}\n";
206 
207             ret ~= "\ntemplate Fields() {\n";
208             ret ~= fields[];
209             ret ~= "\n}\n";
210         }
211         
212         return ret[];
213     }
214 
215     // pragma(msg, getGraphMixinText());
216 
217     mixin(getGraphMixinText());
218 
219     template getTypeIndexOf(T)
220     {
221         mixin("alias getTypeIndexOf = Mappings!()." ~ escapedName!T ~ ";");
222     }
223 
224     // template getAdjacenciesOf(T)
225     // {
226     //     mixin("alias getAdjacenciessOf = Adjacencies[Mappings!()." ~ escapedName!T ~ "];");
227     // }
228 
229     // template getChildTypes(T)
230     // {
231     //     mixin("alias getChildTypes = Commands!()." ~ escapedName!T ~ ";");
232     // }
233 
234     // template getChildCommandFieldsOf(T)
235     // {
236     //     mixin("alias getChildCommandFieldsOf = Fields!()." ~ escapedName!T ~ ";");
237     // }
238 }
239 
240 
241 /// ParentCommandFieldSymbols must be already filtered to have at least
242 /// one member marked with ParentCommand.
243 // template getChildCommandFieldsOf(T, Types...)
244 // {
245     // alias G = Graph!Types;
246  
247     //
248     // I'm keeping the old reflections for the sake of food for thought:
249     //
250     // This implementation is currently close to quadratic, when done for all commands
251     // due to the limitations of metaprogramming in D.
252     // In normal code, you would've done a lookup table by type for all commands,
253     // which you cannot do at compile time in D.
254     // alias ChildCommandFieldsOf = AliasSeq!();
255 
256     // If it really becomes a problem, this could offer a temporary solution.
257     // But ideally, we need a lookup table.
258     // alias RemainingCommandFieldSymbols = AliasSeq!();
259     // alias CommandType = TCommand;
260 
261     // static foreach (field; parentCommandFieldSymbols)
262     // {
263     //     static if (is(typeof(field) : const(TCommand)*))
264     //     {
265     //         ChildCommandFieldsOf = AliasSeq!(ChildCommandFieldsOf, field);
266     //     }
267     //     // else
268     //     // {
269     //     //     RemainingCommandFieldSymbols = AliasSeq!(RemainingCommands, field);
270     //     // }
271     // }
272 // }
273 // unittest
274 // {
275 //     static struct A {}
276 //     static struct B { @ParentCommand A* a; }
277 //     static struct C { @ParentCommand B* b; }
278 
279 //     {
280 //         alias Types = AliasSeq!(A, B, C);
281 //         alias G = TypeGraph!Types;
282 //         static assert(__traits(isSame, G.getChildCommandFieldsOf!A[0], B.a));
283 //         static assert(__traits(isSame, G.getChildCommandFieldsOf!B[0], C.b));
284 //         static assert(G.getChildCommandFieldsOf!C.length == 0);
285 //     }
286 // }
287 
288 // Haven't tested this one, haven't used it either.
289 template AllCommandsOf(Modules...)
290 {
291     template getCommands(alias Module)
292     {
293         template isCommand(string memberName)
294         {
295             import jcli.core;
296             enum isCommand = hasUDA!(__traits(getMember, Module, memberName), Command)
297                 || hasUDA!(__traits(getMember, Module, memberName), CommandDefault);
298         }
299         alias commandNames = Filter!(isCommand, __traits(allMembers, Module));
300 
301         alias getMember(string memberName) = __traits(getMember, Module, memberName);
302         alias getCommands = staticMap!(getMember, commandNames);
303     }
304 
305     alias AllCommandsOf = staticMap!(getCommands, Modules);
306 }
307 
308 // template getParentCommandCandidateFieldSymbols(AllCommands)
309 // {
310 //     alias result = AliasSeq!();
311 //     static foreach (Command; AllCommands)
312 //     {
313 //         static foreach (field; Command.tupleof)
314 //         {
315 //             static if (hasUDA!(field, ParentCommand))
316 //             {
317 //                 static assert(is(typeof(field) : T*, T));
318 //                 result = AliasSeq!(result, field);
319 //             }
320 //         }
321 //     }
322 //     alias getParentCommandCandidateFieldSymbols = result;
323 // }   
324 // unittest
325 // {
326 //     static struct A
327 //     {
328 //         @ParentCommand int* i;
329 //         @ParentCommand int* j;
330 //     }
331 //     static assert(is(GetParentCommandCandidateFieldSymbols!A == AliasSeq!(A.i, A.j)));
332 // }