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 // }