1 /** 2 * D Documentation Generator 3 * Copyright: © 2014 Economic Modeling Specialists, Intl., © 2015 Ferdinand Majerech 4 * Authors: Ferdinand Majerech 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) 6 */ 7 module symboldatabase; 8 9 import std.algorithm; 10 import std.array: popBack, back, empty, popFront; 11 import dparse.ast; 12 import dparse.lexer; 13 import dparse.parser; 14 import std.exception: enforce; 15 import std.range; 16 import std.stdio; 17 import std.string: join, split; 18 19 import allocator; 20 import config; 21 import item; 22 23 24 /** 25 * Gather data about modules to document into a SymbolDatabase and return the database. 26 * 27 * Params: 28 * config = harbored-mod configuration. 29 * writer = Writer (e.g. HTMLWriter), used to determine links for symbols (as Writer 30 * decides where to put symbols). 31 * files = Filenames of all modules to document. 32 * 33 * Returns: SymbolDatabase with collected data. 34 */ 35 SymbolDatabase gatherData(Writer)(ref const(Config) config, Writer writer, const string[] files) 36 { 37 writeln("Collecting data about modules and symbols"); 38 39 auto database = new SymbolDatabase; 40 files.each!(modulePath => gatherModuleData(config, database, writer, modulePath)); 41 database.preCache(); 42 return database; 43 } 44 45 /// 46 class SymbolDatabase 47 { 48 /// Names of modules to document. 49 string[] moduleNames; 50 /// File paths of modules to document. 51 string[] moduleFiles; 52 53 /// `moduleNameToLink["pkg.module"]` gets link to module `pkg.module` 54 string[string] moduleNameToLink; 55 56 /// Cache storing strings used in AST nodes of the parsed modules. 57 private StringCache cache; 58 59 /// Construct a symbol database. 60 this() 61 { 62 cache = StringCache(1024 * 4); 63 } 64 65 /// Get module data for specified module. 66 SymbolDataModule moduleData(string moduleName) 67 { 68 auto mod = moduleName in modules; 69 enforce(mod !is null, new Exception("No such module: " ~ moduleName)); 70 assert(mod.type == SymbolType.Module, "A non-module MembersTree in SymbolDatabase.modules"); 71 return mod.dataModule; 72 } 73 74 //TODO if all the AAs are too slow, try RedBlackTree before completely overhauling 75 76 /** 77 * Get a link to documentation of symbol specified by word (if word is a symbol). 78 * 79 * Searching for a symbol matching to word is done in 3 stages: 80 * 1. Assume word starts by a module name (with or without parent packages of the 81 * module), look for matching modules, and if any, try to find the symbol there. 82 * 2. If 1. didn't find anything, assume word refers to a local symbol (parent 83 * scope - siblings of the symbol being documented or current scope - children 84 * of that symbol). 85 * 3. If 2. didn't find anything, assume word refers to a symbol in any module; 86 * search for a symbol with identical full name (but without the module part) 87 * in all modules. 88 * 89 * Params: 90 * 91 * writer = Writer used to determine links. 92 * scopeStack = Scope of the symbol the documentation of which contains word. 93 * word = Word to cross-reference. 94 * 95 * Returns: link if a matching symbol was found, null otherwise. 96 */ 97 string crossReference(Writer)(Writer writer, string[] scopeStack, string word) 98 { 99 string result; 100 // Don't cross-reference nonsense 101 if (word.splitter(".").empty || word.endsWith(".")) 102 return null; 103 104 string symbolLink(S1, S2)(S1 modStack, S2 symStack) 105 { 106 return writer.symbolLink(symbolStack(modStack, symStack)); 107 } 108 109 /// Does a module with specified name exists? 110 bool moduleExists(string moduleName) 111 { 112 MembersTree* node = &modulesTree; 113 foreach (part; moduleName.splitter(".")) 114 { 115 // can happen if moduleName looks e.g. like "a..b" 116 if (part == "") 117 return false; 118 node = part in node.children; 119 if (node is null) 120 return false; 121 } 122 return node.type == SymbolType.Module; 123 } 124 125 // Search for a nested child with specified name stack in a members tree. 126 // If found, return true and rewrite the members tree pointer. The 127 // optional deleg argument can be used to execute code in each iteration. 128 // 129 // (e.g. for "File.writeln" nameStack would be ["File", "writeln"] and 130 // this would look for members.children["File"].children["writeln"]) 131 bool findNested(Parts)(ref MembersTree* m, Parts nameStack, 132 void delegate(size_t partIdx, MembersTree* members) deleg = null) 133 { 134 auto members = m; 135 size_t partIdx; 136 foreach (part; nameStack) 137 { 138 members = part in members.children; 139 if (!members) 140 return false; 141 if (deleg) 142 deleg(partIdx++, members); 143 } 144 m = members; 145 return true; 146 } 147 148 // If module name is "tharsis.util.traits", this first checks if 149 // word starts with("tharsis.util.traits"), then "util.traits" and 150 // then "traits". 151 bool startsWithPartOf(Splitter)(Splitter wParts, Splitter mParts) 152 { 153 while (!mParts.empty) 154 { 155 if (wParts.startsWith(mParts)) 156 return true; 157 mParts.popFront; 158 } 159 return false; 160 } 161 162 // Search for the symbol in specified module, return true if found. 163 bool searchInModule(string modName) 164 { 165 // Parts of the symbol name within the module. 166 string wordLocal = word; 167 // '.' prefix means module scope - which is what we're 168 // handling here, but need to remove the '.' so we don't 169 // try to look for symbol "". 170 while (wordLocal.startsWith(".")) 171 wordLocal.popFront(); 172 // Remove the part of module name the word starts with. 173 wordLocal.skipOver("."); 174 foreach (part; modName.splitter(".")) 175 if (wordLocal.startsWith(part)) 176 { 177 wordLocal.skipOver(part); 178 wordLocal.skipOver("."); 179 } 180 181 MembersTree* members = modName in modules; 182 assert(members !is null, "Can't search in a nonexistent module"); 183 184 auto parts = wordLocal.split("."); 185 if (!findNested(members, parts)) 186 return false; 187 result = symbolLink(modName.split("."), parts); 188 return true; 189 } 190 191 // Search for a matching symbol assuming word starts by (part of) the name 192 // of the module containing the symbol. 193 bool searchAssumingExplicitModule(ref string result) 194 { 195 // No module name starts by "." - if we use "." we 196 // usually mean a global symbol. 197 if (word.startsWith(".")) 198 return false; 199 200 auto parts = word.splitter("."); 201 // Avoid e.g. "typecons" automatically referencing to std.typecons; 202 // at least 2 parts must be specified (e.g. "std.typecons" or 203 // "typecons.Tuple" but not just "typecons" or "Tuple" ("Tuple" 204 // would still be found by searchInModulesTopLevel)) 205 if (parts.walkLength <= 1) 206 return false; 207 208 // Start by assuming fully qualified name. 209 // If word is fully prefixed by a module name, it almost certainly 210 // refers to that module (unless there is a module the name of which 211 // *ends* with same string in another package and the word refers 212 // to a symbol in *that* module. To handle that very unlikely case, 213 // we don't return false if we fail to find the symbol in the module) 214 string prefix; 215 foreach (part; parts) 216 { 217 prefix ~= part; 218 // Use searchInModule for speed. 219 if (moduleExists(prefix) && searchInModule(prefix)) 220 return true; 221 prefix ~= "."; 222 } 223 224 // If not fully qualified name, assume the name is prefixed at 225 // least by a part of a module name. If it is, look in that module. 226 foreach (modName; modules.byKey) 227 if (startsWithPartOf(parts, modName.splitter("."))) 228 { 229 if (searchInModule(modName)) 230 return true; 231 } 232 233 return false; 234 } 235 236 // Search for a matching symbol in the local scope (scopeStack) - children 237 // of documented symbol and its parent scope - siblings of the symbol. 238 bool searchLocal(ref string result) 239 { 240 // a '.' prefix means we're *not* looking in the local scope. 241 if (word.startsWith(".")) 242 return false; 243 MembersTree* membersScope; 244 MembersTree* membersParent; 245 string thisModule; 246 247 // For a fully qualified name, we need module name (thisModule), 248 // scope containing the symbol (scopeLocal for current scope, 249 // scopeLocal[0 .. $ - 1] for parent scope) *and* symbol name in 250 // the scope. 251 string[] scopeLocal; 252 253 string prefix; 254 foreach (part; scopeStack) 255 { 256 prefix ~= part; 257 scope(exit) 258 prefix ~= "."; 259 if (!moduleExists(prefix)) 260 continue; 261 thisModule = prefix; 262 263 scopeLocal = scopeStack; 264 scopeLocal.skipOver(thisModule.splitter(".")); 265 266 MembersTree* members = &modules[thisModule]; 267 void saveScopes(size_t depth, MembersTree* members) 268 { 269 const maxDepth = scopeLocal.length; 270 if (depth == maxDepth - 1) 271 membersScope = members; 272 else if (depth == maxDepth - 2) 273 membersParent = members; 274 } 275 if (findNested(members, scopeLocal, &saveScopes)) 276 break; 277 } 278 279 // Search for the symbol specified by word in a members tree. 280 // This assumes word directly names a member of the tree. 281 bool searchMembers(string[] scope_, MembersTree* members) 282 { 283 auto parts = word.split("."); 284 if (!findNested(members, parts)) 285 return false; 286 result = symbolLink(thisModule.split("."), scope_ ~ parts); 287 return true; 288 } 289 290 if (membersScope && searchMembers(scopeLocal, membersScope)) 291 return true; 292 if (membersParent && searchMembers(scopeLocal[0 .. $ - 1], membersParent)) 293 return true; 294 295 return false; 296 } 297 298 // Search for a matching symbol in top-level scopes of all modules. For a 299 // non-top-level sumbol to match, it must be prefixed by a top-level symbol, 300 // e.g. "Array.clear" instead of just "clear" 301 bool searchInModulesTopLevel(ref string result) 302 { 303 string wordLocal = word; 304 // '.' prefix means module scope - which is what we're 305 // handling here, but need to remove the '.' so we don't 306 // try to look for symbol "". 307 while (wordLocal.startsWith(".")) 308 wordLocal.popFront(); 309 auto parts = wordLocal.split("."); 310 311 // Search in top-level scopes of each module. 312 foreach (moduleName, ref MembersTree membersRef; modules) 313 { 314 MembersTree* members = &membersRef; 315 if (!findNested(members, parts)) 316 continue; 317 318 result = symbolLink(moduleName.split("."), parts); 319 return true; 320 } 321 return false; 322 } 323 324 if (searchAssumingExplicitModule(result) || searchLocal(result) || 325 searchInModulesTopLevel(result)) 326 { 327 return result; 328 } 329 330 return null; 331 } 332 333 /** Get a range describing a symbol with specified name. 334 * 335 * Params: 336 * 337 * moduleStack = Module name stack (module name split by "."). 338 * symbolStack = Symbol name stack (symbol name in the module split by "."). 339 * 340 * Returns: An InputRange describing the fully qualified symbol name. 341 * Every item of the range will be a struct describing a part of the 342 * name, with `string name` and `SymbolType type` members. 343 * E.g. for `"std.stdio.File"` the range items would be 344 * `{name: "std", type: Package}, {name: "stdio", type: Module}, 345 * {name: "File", type: Class}`. 346 * 347 * Note: If the symbol does not exist, the returned range will only contain 348 * items for parent symbols that do exist (e.g. if moduleStack is 349 * ["std", "stdio"], symbolStack is ["noSuchThing"]), the symbolStack 350 * will describe the "std" package and "stdio" module, but will contain 351 * no entry for "noSuchThing". 352 * 353 */ 354 auto symbolStack(S1, S2)(S1 moduleStack, S2 symbolStack) 355 { 356 assert(!moduleStack.empty, "Can't get a symbol stack with no module stack"); 357 358 static struct SymbolStack 359 { 360 private: 361 SymbolDatabase database; 362 S1 moduleStack; 363 S2 symbolStack; 364 365 MembersTree* currentSymbol; 366 string moduleName; 367 368 this(SymbolDatabase db, S1 modStack, S2 symStack) 369 { 370 database = db; 371 moduleStack = modStack; 372 symbolStack = symStack; 373 delve(false); 374 } 375 public: 376 auto front() 377 { 378 assert(!empty, "Can't get front of an empty range"); 379 struct Result 380 { 381 string name; 382 SymbolType type; 383 } 384 return Result(moduleStack.empty ? symbolStack.front : moduleStack.front, 385 currentSymbol.type); 386 } 387 388 void popFront() 389 { 390 assert(!empty, "Can't pop front of an empty range"); 391 if (!moduleStack.empty) 392 { 393 moduleStack.popFront(); 394 delve(moduleStack.empty); 395 } 396 else 397 { 398 symbolStack.popFront(); 399 delve(false); 400 } 401 } 402 403 bool empty() 404 { 405 return currentSymbol is null; 406 } 407 408 void delve(bool justFinishedModule) 409 { 410 if (!moduleStack.empty) with(database) 411 { 412 if (!moduleName.empty) 413 moduleName ~= "."; 414 moduleName ~= moduleStack.front; 415 currentSymbol = currentSymbol is null 416 ? (moduleStack.front in modulesTree.children) 417 : (moduleStack.front in currentSymbol.children); 418 return; 419 } 420 if (!symbolStack.empty) 421 { 422 if (justFinishedModule) with(database) 423 { 424 currentSymbol = moduleName in modules; 425 assert(currentSymbol !is null, "A module that's in moduleTree must be in modules too"); 426 } 427 currentSymbol = symbolStack.front in currentSymbol.children; 428 return; 429 } 430 currentSymbol = null; 431 } 432 } 433 434 return SymbolStack(this, moduleStack, symbolStack); 435 } 436 437 private: 438 /** Pre-compute any data structures needed for fast cross-referencing. 439 * 440 * Currently used for modulesTree, which allows quick decisions on whether a 441 * module exists. 442 */ 443 void preCache() 444 { 445 foreach (name; modules.byKey) 446 { 447 auto parts = name.splitter("."); 448 MembersTree* node = &modulesTree; 449 foreach (part; parts) 450 { 451 node.type = SymbolType.Package; 452 MembersTree* child = part in node.children; 453 if (child is null) 454 { 455 node.children[part] = MembersTree.init; 456 child = part in node.children; 457 } 458 node = child; 459 } 460 // The leaf nodes of the module tree are packages. 461 node.type = SymbolType.Module; 462 } 463 } 464 465 /// Member trees of all modules, indexed by full module names. 466 MembersTree[string] modules; 467 468 /// Allows to quickly determine whether a module exists. Built by preCache. 469 MembersTree modulesTree; 470 471 /// Get members of symbol with specified name stack in specified module. 472 MembersTree* getMembers(string moduleName, string[] symbolStack) 473 { 474 MembersTree* members = &modules[moduleName]; 475 symbolStack.each!(namePart => members = &members.children[namePart]); 476 return members; 477 } 478 } 479 480 /// Enumberates types of symbols in the symbol database. 481 enum SymbolType : ubyte 482 { 483 /// A package with no module file (package.d would be considered a module). 484 Package, 485 /// A module. 486 Module, 487 /// An alias. 488 Alias, 489 /// An enum. 490 Enum, 491 /// A class. 492 Class, 493 /// A struct. 494 Struct, 495 /// An interface. 496 Interface, 497 /// A function (including e.g. constructors). 498 Function, 499 /// A template (not a template function/template class/etc). 500 Template, 501 /// Only used for enum members at the moment. 502 Value, 503 /// A variable member. 504 Variable 505 } 506 507 /// Data we keep track of for a module. 508 struct SymbolDataModule 509 { 510 /// Summary comment of the module, *not* processes by Markdown. 511 string summary; 512 } 513 514 private: 515 516 // Reusing Members here is a very quick hack, and we may need something better than a 517 // tree of AA's if generating docs for big projects is too slow. 518 /// Recursive tree of all members of a symbol. 519 struct MembersTree 520 { 521 /// Members of children of this tree node. 522 MembersTree[string] children; 523 524 /// Type of this symbol. 525 SymbolType type; 526 527 union 528 { 529 /// Data specific for a module symbol. 530 SymbolDataModule dataModule; 531 //TODO data for any other symbol types. In a union to save space. 532 } 533 } 534 535 536 /** Gather data about symbols in a module into a SymbolDatabase. 537 * 538 * Writes directly into the passed SymbolDatabase. 539 * 540 * Params: 541 * 542 * config = harbored-mod configuration. 543 * database = SymbolDatabase to gather data into. 544 * writer = Writer (e.g. HTMLWriter), used to determine links for symbols (as Writer 545 * decides where to put symbols). 546 * modulePath = Path of the module file. 547 */ 548 void gatherModuleData(Writer) 549 (ref const(Config) config, SymbolDatabase database, Writer writer, string modulePath) 550 { 551 // Load the module file. 552 import std.file : readText, FileException; 553 ubyte[] fileBytes; 554 try 555 fileBytes = cast(ubyte[]) modulePath.readText!(char[]); 556 catch (FileException e) 557 { 558 writefln("Failed to load file %s: will be ignored", modulePath); 559 return; 560 } 561 562 // Parse the module. 563 LexerConfig lexConfig; 564 lexConfig.fileName = modulePath; 565 lexConfig.stringBehavior = StringBehavior.source; 566 import main : ignoreParserError; 567 import dparse.rollback_allocator : RollbackAllocator; 568 569 RollbackAllocator allocator; 570 Module m = fileBytes.getTokensForParser(lexConfig, &database.cache) 571 .parseModule(modulePath, &allocator, &ignoreParserError); 572 573 // Gather data. 574 auto visitor = new DataGatherVisitor!Writer(config, database, writer, modulePath); 575 visitor.visit(m); 576 } 577 578 /** Visits AST nodes to gather data about symbols in a module. 579 */ 580 class DataGatherVisitor(Writer) : ASTVisitor 581 { 582 /** Construct a DataGatherVisitor. 583 * Params: 584 * 585 * config = Configuration data, including macros and the output directory. 586 * database = Database to gather data into. 587 * writer = Used to determine link strings. 588 * fileName = Module file name. 589 */ 590 this(ref const Config config, SymbolDatabase database, Writer writer, string fileName) 591 { 592 this.config = &config; 593 this.database = database; 594 this.writer = writer; 595 this.fileName = fileName; 596 } 597 598 alias visit = ASTVisitor.visit; 599 600 /// Determines module name and adds a MemberTree for it to the database. 601 override void visit(const Module mod) 602 { 603 import std.range : chain, iota, join, only; 604 import std.conv : to; 605 606 if (mod.moduleDeclaration is null) 607 { 608 writefln("Ignoring file %s: no 'module' declaration", fileName); 609 return; 610 } 611 auto stack = cast(string[])mod.moduleDeclaration.moduleName.identifiers.map!(a => a.text).array; 612 613 foreach (exclude; config.excludes) 614 { 615 // If module name is pkg1.pkg2.mod, we first check 616 // "pkg1", then "pkg1.pkg2", then "pkg1.pkg2.mod" 617 // i.e. we only check for full package/module names. 618 if (iota(stack.length + 1).map!(l => stack[0 .. l].join(".")).canFind(exclude)) 619 { 620 writeln("Excluded module ", stack.join(".")); 621 return; 622 } 623 } 624 625 moduleName = stack.join(".").to!string; 626 database.moduleNames ~= moduleName; 627 database.moduleFiles ~= fileName; 628 database.moduleNameToLink[moduleName] = writer.moduleLink(stack); 629 database.modules[moduleName] = MembersTree.init; 630 631 database.modules[moduleName].type = SymbolType.Module; 632 database.modules[moduleName].dataModule.summary = 633 commentSummary(mod.moduleDeclaration.comment); 634 635 mod.accept(this); 636 } 637 638 /// Gather data about various members /// 639 640 override void visit(const EnumDeclaration ed) 641 { 642 visitAggregateDeclaration!(SymbolType.Enum)(ed); 643 } 644 645 // Document all enum members even if they have no doc comments. 646 override void visit(const EnumMember member) 647 { 648 // Link to the enum owning the member (enum members themselves have no 649 // files/detailed explanations). 650 pushSymbol(member.name.text, SymbolType.Value); 651 scope(exit) popSymbol(); 652 } 653 654 override void visit(const ClassDeclaration cd) 655 { 656 visitAggregateDeclaration!(SymbolType.Class)(cd); 657 } 658 659 override void visit(const TemplateDeclaration td) 660 { 661 visitAggregateDeclaration!(SymbolType.Template)(td); 662 } 663 664 override void visit(const StructDeclaration sd) 665 { 666 visitAggregateDeclaration!(SymbolType.Struct)(sd); 667 } 668 669 override void visit(const InterfaceDeclaration id) 670 { 671 visitAggregateDeclaration!(SymbolType.Interface)(id); 672 } 673 674 override void visit(const AliasDeclaration ad) 675 { 676 if (ad.comment is null) 677 return; 678 679 if (ad.declaratorIdentifierList !is null) 680 foreach (name; ad.declaratorIdentifierList.identifiers) 681 { 682 pushSymbol(name.text, SymbolType.Alias); 683 scope(exit) popSymbol(); 684 } 685 else foreach (initializer; ad.initializers) 686 { 687 pushSymbol(initializer.name.text, SymbolType.Alias); 688 scope(exit) popSymbol(); 689 } 690 } 691 692 override void visit(const VariableDeclaration vd) 693 { 694 foreach (const Declarator dec; vd.declarators) 695 { 696 if (vd.comment is null && dec.comment is null) 697 continue; 698 pushSymbol(dec.name.text, SymbolType.Variable); 699 scope(exit) popSymbol(); 700 } 701 if (vd.comment !is null && vd.autoDeclaration !is null) 702 { 703 foreach (part; vd.autoDeclaration.parts) with (part) 704 { 705 pushSymbol(identifier.text, SymbolType.Variable); 706 scope(exit) popSymbol(); 707 string[] storageClasses; 708 vd.storageClasses.each!(stor => storageClasses ~= str(stor.token.type)); 709 } 710 } 711 } 712 713 override void visit(const Constructor cons) 714 { 715 if (cons.comment is null) 716 return; 717 visitFunctionDeclaration("this", cons); 718 } 719 720 override void visit(const FunctionDeclaration fd) 721 { 722 if (fd.comment is null) 723 return; 724 visitFunctionDeclaration(fd.name.text, fd); 725 } 726 727 // Optimization: don't allow visit() for these AST nodes to result in visit() 728 // calls for their subnodes. This avoids most of the dynamic cast overhead. 729 override void visit(const AssignExpression assignExpression) {} 730 override void visit(const CmpExpression cmpExpression) {} 731 override void visit(const TernaryExpression ternaryExpression) {} 732 override void visit(const IdentityExpression identityExpression) {} 733 override void visit(const InExpression inExpression) {} 734 735 private: 736 /** If the comment starts with a summary, return it, otherwise return null. 737 * 738 * Note: as libdparse does not seem to recognize summaries correctly (?), 739 * we simply assume the first section of the comment to be the summary. 740 */ 741 string commentSummary(string comment) 742 { 743 if (comment.empty) 744 return null; 745 746 import core.exception: RangeError; 747 try 748 { 749 import ddoc.comments : Comment, parseComment; 750 auto app = appender!string(); 751 if (comment.length >= 3) 752 comment.unDecorateComment(app); 753 754 Comment c = parseComment(app.data, cast(string[string])config.macros); 755 if (c.sections.length) 756 return c.sections[0].content; 757 } 758 catch (RangeError e) 759 { 760 writeln("RangeError"); 761 // Writer.readAndWriteComment will catch this too and 762 // write an error message. Not kosher to catch Errors 763 // but unfortunately needed with libdparse ATM (2015). 764 return null; 765 } 766 return null; 767 } 768 769 void visitAggregateDeclaration(SymbolType type, A)(const A ad) 770 { 771 if (ad.comment is null) 772 return; 773 774 // pushSymbol will push to stack, add tree entry and return MembersTree 775 // containing that entry so we can also add the aggregate to the correct 776 // Item array 777 pushSymbol(ad.name.text, type); 778 scope(exit) popSymbol(); 779 780 ad.accept(this); 781 } 782 783 void visitFunctionDeclaration(Fn)(string name, Fn fn) 784 { 785 pushSymbol(name, SymbolType.Function); 786 scope(exit) popSymbol(); 787 788 string fdName; 789 static if (__traits(hasMember, typeof(fn), "name")) 790 fdName = fn.name.text; 791 else 792 fdName = "this"; 793 fn.accept(this); 794 } 795 796 /** Push a symbol to the stack, moving into its scope. 797 * 798 * Params: 799 * 800 * name = The symbol's name 801 * type = Type of the symbol. 802 * 803 * Returns: Tree of the *parent* symbol of the pushed symbol. 804 */ 805 MembersTree* pushSymbol(string name, SymbolType type) 806 { 807 auto parentStack = symbolStack; 808 symbolStack ~= name; 809 810 MembersTree* members = database.getMembers(moduleName, parentStack); 811 if(!(name in members.children)) 812 { 813 members.children[name] = MembersTree.init; 814 members.children[name].type = type; 815 } 816 return members; 817 } 818 819 /// Leave scope of a symbol, moving back to its parent. 820 void popSymbol() 821 { 822 symbolStack.popBack(); 823 } 824 825 /// Harbored-mod configuration. 826 const(Config)* config; 827 /// Database we're gathering data into. 828 SymbolDatabase database; 829 /// Used to determine links to symbols. 830 Writer writer; 831 /// Filename of this module. 832 string fileName; 833 /// Name of this module in D code. 834 string moduleName; 835 /** Namespace stack of the current symbol, without the package/module name. 836 * 837 * E.g. ["Class", "member"] 838 */ 839 string[] symbolStack; 840 }