1 module jcli.commandgraph.graph; 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, Subcommands; 19 20 import std.traits; 21 import std.meta; 22 23 template escapedName(T) 24 { 25 import std.conv : to; 26 import std.path : baseName; 27 import std.algorithm; 28 import std.range; 29 30 // The idea here is to minimize collisions between type symbols. 31 enum location = __traits(getLocation, T); 32 enum escapedName = baseName(location[0]) 33 .chain(fullyQualifiedName!T) 34 .map!(ch => canFind([cast(dchar) '.', ' ', '!', '(', ')'], ch) ? '_' : ch) 35 .chain(location[1].to!string) 36 .to!string; 37 } 38 39 struct TypeGraphNode 40 { 41 int typeIndex; 42 43 /// Shows which field of the given command type points to the parent context. 44 /// Is -1 if the related command does not have a member pointing to the parent context. 45 int fieldIndex; 46 } 47 48 /// Represents a command type graph with only one type. 49 template DegenerateCommandTypeGraph(CommandType) 50 { 51 alias Types = AliasSeq!CommandType; 52 immutable TypeGraphNode[][1] adjacencies = [[]]; 53 immutable int[] rootTypeIndices = [0]; 54 55 template getTypeIndexOf(T) 56 { 57 static assert(is(T == CommandType)); 58 enum int getTypeIndexOf = 0; 59 } 60 } 61 62 /// Represents a command type graph where the information about the parent-child 63 /// relationships of commands are drawn from the children, via the @ParentCommand UDA. 64 template BottomUpCommandTypeGraph(_Types...) 65 { 66 alias Node = TypeGraphNode; 67 alias Types = _Types; 68 69 // Mappings, rootTypeIndices, adjacencies 70 mixin(getGraphMixinText()); 71 // pragma(msg, getGraphMixinText()); 72 73 template getTypeIndexOf(T) 74 { 75 mixin("alias getTypeIndexOf = Mappings!()." ~ escapedName!T ~ ";"); 76 } 77 78 // Note: 79 // I'm realying on the idea that name lookup in scopes is linear in time to 80 // essentially have a fake compile time AA. 81 // Normal AA's are not allowed to be used at compile time rn. 82 // (E.g. `immutable int[int] example = [1: 2]` does not compile). 83 // TODO: refactor to return an info struct, like the top-down graph below. 84 private string getGraphMixinText() 85 { 86 size_t[string] typeToIndex; 87 static foreach (index, Type; Types) 88 typeToIndex[escapedName!Type] = index; 89 Node[][Types.length] childrenGraph; 90 91 foreach (outerIndex, Type; Types) 92 { 93 static foreach (fieldIndex, field; Type.tupleof) 94 { 95 static if (is(typeof(field) : T*, T)) 96 { 97 static if (hasUDA!(field, ParentCommand)) 98 {{ 99 const name = escapedName!T; 100 if (auto index = name in typeToIndex) 101 { 102 // A command being a child of itself is not detected anywhere else, it's an edge case. 103 assert(*index != outerIndex, "`" ~ T.stringof ~ "` cannot be a parent of itself."); 104 105 childrenGraph[*index] ~= Node(cast(int) outerIndex, cast(int) fieldIndex); 106 } 107 else 108 { 109 assert(false, "`" ~ T.stringof ~ "` not found among types."); 110 } 111 }} 112 } 113 } 114 } 115 116 // TODO: is bit array worth it? 117 bool [Types.length] isNotRootCache; 118 foreach (parentIndex, children; childrenGraph) 119 { 120 foreach (childNode; children) 121 isNotRootCache[childNode.typeIndex] = true; 122 } 123 124 size_t[] rootTypeIndices; 125 foreach (index, isNotRoot; isNotRootCache) 126 { 127 if (!isNotRoot) 128 rootTypeIndices ~= index; 129 } 130 131 checkNodeAcessibility(childrenGraph[], rootTypeIndices); 132 133 { 134 // Check for cicles in the graph. 135 // Cicles will stifle the compiler (but I'm not sure). 136 // TODO: use bit array? 137 bool[Types.length] visited = false; 138 139 bool isCyclic(size_t index) 140 { 141 if (visited[index]) 142 return true; 143 144 visited[index] = true; 145 foreach (childNode; childrenGraph[index]) 146 { 147 if (isCyclic(childNode.typeIndex)) 148 return true; 149 } 150 return false; 151 } 152 153 foreach (parentIndex, isNotRoot; isNotRootCache) 154 { 155 if (isNotRoot) 156 continue; 157 visited[] = false; 158 if (isCyclic(parentIndex)) 159 { 160 // TODO: report more info here. 161 assert(false, "The command graph contains a cycle"); 162 } 163 } 164 } 165 166 import std.array : appender; 167 import std.format : formattedWrite; 168 import std.algorithm : map; 169 170 auto ret = appender!string; 171 172 { 173 ret ~= "template Mappings() {"; 174 foreach (key, index; typeToIndex) 175 formattedWrite(ret, "enum int %s = %d;", key, index); 176 ret ~= "}"; 177 } 178 179 // TODO: return normally 180 { 181 formattedWrite(ret, "immutable int[] rootTypeIndices = %s;\n", rootTypeIndices); 182 } 183 { 184 formattedWrite(ret, "immutable Node[][] adjacencies = %s;\n", childrenGraph); 185 } 186 187 // Extra info, not actually used 188 static if (0) 189 { 190 formattedWrite(ret, "immutable bool[] isNodeRoot = %s;\n", isNotRootCache[].map!"!a"); 191 192 auto types = appender!string; 193 auto fields = appender!string; 194 195 foreach (parentIndex, ParentType; Types) 196 { 197 types ~= "alias " ~ escapedName!ParentType ~ " = AliasSeq!("; 198 fields ~= "alias " ~ escapedName!ParentType ~ " = AliasSeq!("; 199 200 foreach (typeIndexIndex, childNode; childrenGraph[parentIndex]) 201 { 202 if (typeIndexIndex > 0) 203 { 204 types ~= ", "; 205 fields ~= ", "; 206 } 207 208 formattedWrite(fields, "Types[%d].tupleof[%d]", 209 childNode.typeIndex, childNode.fieldIndex); 210 211 formattedWrite(types, "Types[%d]", 212 childNode.typeIndex); 213 } 214 215 types ~= ");\n"; 216 fields ~= ");\n"; 217 } 218 219 ret ~= "\ntemplate Commands() {\n"; 220 ret ~= types[]; 221 ret ~= "\n}\n"; 222 223 ret ~= "\ntemplate Fields() {\n"; 224 ret ~= fields[]; 225 ret ~= "\n}\n"; 226 } 227 228 return ret[]; 229 } 230 } 231 232 unittest 233 { 234 { 235 static struct A {} 236 static struct B { @ParentCommand A* a; } 237 alias Graph = BottomUpCommandTypeGraph!(A, B); 238 static assert(is(Graph.Types == AliasSeq!(A, B))); 239 240 enum AIndex = Graph.getTypeIndexOf!A; 241 static assert(AIndex == 0); 242 243 enum BIndex = Graph.getTypeIndexOf!B; 244 static assert(BIndex == 1); 245 246 static assert(Graph.adjacencies[AIndex] == [TypeGraphNode(BIndex, 0)]); 247 static assert(Graph.adjacencies[BIndex].length == 0); 248 249 static assert(Graph.rootTypeIndices == [AIndex]); 250 } 251 252 // Cycle detection 253 { 254 static struct A { @ParentCommand A* a; } 255 static assert(!__traits(compiles, BottomUpCommandTypeGraph!A)); 256 } 257 258 template Cycle() 259 { 260 struct A0 { @ParentCommand B0* b; } 261 struct B0 { @ParentCommand A0* a; } 262 static assert(!__traits(compiles, BottomUpCommandTypeGraph!(A0, B0))); 263 } 264 265 alias a = Cycle!(); 266 } 267 268 269 270 // Haven't tested this one, haven't used it either. 271 template AllCommandsOf(Modules...) 272 { 273 template getCommands(alias Module) 274 { 275 template isCommand(string memberName) 276 { 277 import jcli.core; 278 enum isCommand = hasUDA!(__traits(getMember, Module, memberName), Command) 279 || hasUDA!(__traits(getMember, Module, memberName), CommandDefault); 280 } 281 alias commandNames = Filter!(isCommand, __traits(allMembers, Module)); 282 283 alias getMember(string memberName) = __traits(getMember, Module, memberName); 284 alias getCommands = staticMap!(getMember, commandNames); 285 } 286 287 alias AllCommandsOf = staticMap!(getCommands, Modules); 288 } 289 290 291 /// Represents a command type graph where the information about the parent-child 292 /// relationships of commands are drawn from the parents, via the @Subcommands UDA. 293 template TopDownCommandTypeGraph(RootTypes...) 294 { 295 alias Node = TypeGraphNode; 296 private alias AllTypes = AllSubcommandsOf!RootTypes; 297 298 private immutable graphData = getGraphData(); 299 300 // Types, Mappings 301 mixin(graphData.mixinText); 302 immutable Node[][] adjacencies = graphData.childrenGraph; 303 immutable size_t[] rootTypeIndices = graphData.rootTypeIndices; 304 305 template getTypeIndexOf(T) 306 { 307 mixin("alias getTypeIndexOf = Mappings!()." ~ escapedName!T ~ ";"); 308 } 309 310 private auto getGraphData() 311 { 312 static struct Result 313 { 314 string mixinText; 315 Node[][] childrenGraph; 316 size_t[] rootTypeIndices; 317 } 318 319 // This thing could contain duplicates! 320 // Which is why I'm doing all of the remapping below. 321 // It's still a bad idea to map to a command from two places, because then this array 322 // will contain ALL of the subcommands of that type both times! 323 324 size_t[string] typeToIndex; 325 size_t[] actualTypeIndexToAllTypeIndex; 326 327 static foreach (index, Type; AllTypes) 328 {{ 329 if (escapedName!Type !in typeToIndex) 330 { 331 typeToIndex[escapedName!Type] = actualTypeIndexToAllTypeIndex.length; 332 actualTypeIndexToAllTypeIndex ~= index; 333 } 334 }} 335 336 size_t numTypes = actualTypeIndexToAllTypeIndex.length; 337 auto childrenGraph = new Node[][](numTypes); 338 339 static foreach (index, ParentType; AllTypes) 340 {{ 341 size_t actualTypeIndex = typeToIndex[escapedName!ParentType]; 342 343 if (actualTypeIndexToAllTypeIndex[actualTypeIndex] == index) 344 { 345 static foreach (Subcommand; SubcommandsOf!ParentType) 346 {{ 347 int fieldIndex = getIndexOfFieldWithParentPointerType!(ParentType, Subcommand); 348 size_t subcommandActualTypeIndex = typeToIndex[escapedName!Subcommand]; 349 childrenGraph[actualTypeIndex] ~= Node(cast(int) subcommandActualTypeIndex, cast(int) fieldIndex); 350 }} 351 } 352 }} 353 354 size_t[] rootTypeIndices; // @suppress(dscanner.suspicious.label_var_same_name) 355 foreach (RootType; RootTypes) 356 rootTypeIndices ~= typeToIndex[escapedName!RootType]; 357 358 checkNodeAcessibility(childrenGraph[], rootTypeIndices); 359 360 import std.array : appender; 361 import std.format : formattedWrite; 362 import std.algorithm : map; 363 364 auto ret = appender!string; 365 366 { 367 ret ~= "template Mappings() {"; 368 foreach (key, index; typeToIndex) 369 formattedWrite!"enum int %s = %d;"(ret, key, index); 370 ret ~= "}"; 371 } 372 373 { 374 ret ~= "\n"; 375 ret ~= "alias Types = AliasSeq!("; 376 size_t appendedCount = 0; 377 static foreach (index, Type; AllTypes) 378 {{ 379 size_t actualIndex = typeToIndex[escapedName!Type]; 380 if (actualTypeIndexToAllTypeIndex[actualIndex] == index) 381 { 382 if (appendedCount > 0) 383 ret ~= ","; 384 385 assert(appendedCount == actualIndex); 386 appendedCount++; 387 388 formattedWrite!"AllTypes[%d]"(ret, actualTypeIndexToAllTypeIndex[actualIndex]); 389 } 390 }} 391 ret ~= ");"; 392 ret ~= "\n"; 393 } 394 395 return Result(ret[], childrenGraph, rootTypeIndices); 396 } 397 } 398 399 unittest 400 { 401 template Basic() 402 { 403 @(Subcommands!B) 404 static struct A {} 405 406 static struct B { A* a; } 407 408 alias Graph = TopDownCommandTypeGraph!(A); 409 static assert(is(Graph.Types == AliasSeq!(A, B))); 410 411 enum AIndex = 0; 412 static assert(AIndex == Graph.getTypeIndexOf!A); 413 414 enum BIndex = 1; 415 static assert(BIndex == Graph.getTypeIndexOf!B); 416 417 enum node = TypeGraphNode(BIndex, 0); 418 static assert(Graph.adjacencies[AIndex].length == 1); 419 static assert(Graph.adjacencies[AIndex][0] == node); 420 static assert(Graph.adjacencies[BIndex].length == 0); 421 422 static assert(Graph.rootTypeIndices == [AIndex]); 423 } 424 425 // Cycle detection 426 template ParentOfSelf() 427 { 428 @(Subcommands!A) 429 static struct A { A* a; } 430 static assert(!__traits(compiles, TopDownCommandTypeGraph!A)); 431 } 432 433 template Cycle() 434 { 435 @(Subcommands!B) 436 static struct A { B* b; } 437 @(Subcommands!A) 438 static struct B { A* a; } 439 static assert(!__traits(compiles, TopDownCommandTypeGraph!(A, B))); 440 static assert(!__traits(compiles, TopDownCommandTypeGraph!(A))); 441 static assert(!__traits(compiles, TopDownCommandTypeGraph!(B))); 442 } 443 444 template NoField() 445 { 446 @(Subcommands!B) 447 static struct A {} 448 static struct B {} 449 450 alias Graph = TopDownCommandTypeGraph!(A); 451 enum AIndex = Graph.getTypeIndexOf!A; 452 enum BIndex = Graph.getTypeIndexOf!B; 453 454 enum node = TypeGraphNode(BIndex, -1); 455 static assert(Graph.adjacencies[AIndex].length == 1); 456 static assert(Graph.adjacencies[AIndex][0] == node); 457 } 458 459 template DuplicateParents() 460 { 461 @(Subcommands!C) 462 static struct A {} 463 @(Subcommands!C) 464 static struct B {} 465 static struct C {A* a; B* b;} 466 467 alias Graph = TopDownCommandTypeGraph!(A, B); 468 enum AIndex = Graph.getTypeIndexOf!A; 469 enum BIndex = Graph.getTypeIndexOf!B; 470 enum CIndex = Graph.getTypeIndexOf!C; 471 static assert(Graph.Types.length == 3); 472 473 enum nodeParentA = TypeGraphNode(CIndex, 0); 474 enum nodeParentB = TypeGraphNode(CIndex, 1); 475 476 static assert(Graph.adjacencies[AIndex].length == 1); 477 static assert(Graph.adjacencies[AIndex][0] == nodeParentA); 478 479 static assert(Graph.adjacencies[BIndex].length == 1); 480 static assert(Graph.adjacencies[BIndex][0] == nodeParentB); 481 482 static assert(Graph.adjacencies[CIndex].length == 0); 483 } 484 485 { 486 alias _ = Basic!(); 487 } 488 { 489 alias _ = ParentOfSelf!(); 490 } 491 { 492 alias _ = Cycle!(); 493 } 494 { 495 alias _ = NoField!(); 496 } 497 { 498 alias _ = DuplicateParents!(); 499 } 500 } 501 502 503 template SubcommandsOf(CommandType) 504 { 505 alias _Subcommands = getUDAs!(CommandType, Subcommands); 506 alias getTypes(Attr : Subcommands!(Types), Types...) = Types; 507 alias SubcommandsOf = staticMap!(getTypes, _Subcommands); 508 } 509 unittest 510 { 511 { 512 static struct A {} 513 static assert(is(SubcommandsOf!A == AliasSeq!())); 514 } 515 { 516 static struct A {} 517 @(Subcommands!A) 518 static struct B {} 519 static assert(is(SubcommandsOf!B == AliasSeq!A)); 520 } 521 { 522 static struct A1 {} 523 static struct A2 {} 524 static struct A3 {} 525 526 @(Subcommands!(A1, A2)) 527 @(Subcommands!(A3)) 528 static struct B {} 529 530 static assert(is(SubcommandsOf!(B) == AliasSeq!(A1, A2, A3))); 531 } 532 } 533 534 template AllSubcommandsOf(CommandTypes...) 535 { 536 static if (CommandTypes.length == 1) 537 { 538 alias AllSubcommandsOf = AliasSeq!( 539 CommandTypes[0], staticMap!(.AllSubcommandsOf, SubcommandsOf!(CommandTypes[0]))); 540 } 541 else static if (CommandTypes.length == 0) 542 { 543 alias AllSubcommandsOf = AliasSeq!(); 544 } 545 else 546 { 547 alias AllSubcommandsOf = staticMap!(.AllSubcommandsOf, CommandTypes); 548 } 549 } 550 unittest 551 { 552 template Cycle() 553 { 554 @(Subcommands!B) 555 static struct A {} 556 @(Subcommands!A) 557 static struct B {} 558 static assert(!__traits(compiles, AllSubcommandsOf!A)); 559 static assert(!__traits(compiles, AllSubcommandsOf!B)); 560 static assert(!__traits(compiles, AllSubcommandsOf!(A, B))); 561 } 562 563 { 564 static struct A {} 565 static assert(is(AllSubcommandsOf!A == AliasSeq!(A))); 566 } 567 568 template SelfIncluded() 569 { 570 static struct A {} 571 @(Subcommands!A) 572 static struct B {} 573 static assert(is(AllSubcommandsOf!B == AliasSeq!(B, A))); 574 } 575 576 { 577 alias _ = Cycle!(); 578 } 579 { 580 alias _ = SelfIncluded!(); 581 } 582 583 } 584 585 586 private int getIndexOfFieldWithParentPointerType(ParentType, ChildType)() 587 { 588 int result = -1; 589 static foreach (SubcommandType; SubcommandsOf!ParentType) 590 { 591 static foreach (fieldIndex, field; SubcommandType.tupleof) 592 {{ 593 static if (is(typeof(field) : T*, T)) 594 { 595 // TODO: ParentCommand uda checks 596 static if (is(T == ParentType)) 597 { 598 result = cast(int) fieldIndex; 599 } 600 } 601 }} 602 } 603 return result; 604 } 605 606 unittest 607 { 608 { 609 static struct A {} 610 static assert(is(AllSubcommandsOf!A == AliasSeq!(A))); 611 } 612 { 613 static struct A {} 614 static struct B {} 615 static assert(is(AllSubcommandsOf!(A, B) == AliasSeq!(A, B))); 616 } 617 { 618 static struct B {} 619 static struct C {} 620 621 @(Subcommands!(B, C)) 622 static struct A {} 623 624 static assert(is(AllSubcommandsOf!(A) == AliasSeq!(A, B, C))); 625 } 626 } 627 628 629 private void checkNodeAcessibility(const TypeGraphNode[][] adjacencies, const size_t[] rootTypeIndices) 630 { 631 bool[] isAccessibleCache = new bool[adjacencies.length]; 632 633 void markChildrenAccessible(size_t index) 634 { 635 foreach (childNode; adjacencies[index]) 636 { 637 if (isAccessibleCache[childNode.typeIndex]) 638 continue; 639 isAccessibleCache[childNode.typeIndex] = true; 640 markChildrenAccessible(childNode.typeIndex); 641 } 642 } 643 644 foreach (parentIndex; rootTypeIndices) 645 { 646 isAccessibleCache[parentIndex] = true; 647 markChildrenAccessible(parentIndex); 648 } 649 650 import std.algorithm : all; 651 // TODO: better error here. 652 assert(all(isAccessibleCache[]), "Some types were inaccessible."); 653 }