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, 426 "A module that's in moduleTree " ~ 427 "must be in modules too"); 428 } 429 currentSymbol = symbolStack.front in currentSymbol.children; 430 return; 431 } 432 currentSymbol = null; 433 } 434 } 435 436 return SymbolStack(this, moduleStack, symbolStack); 437 } 438 439 private: 440 /** Pre-compute any data structures needed for fast cross-referencing. 441 * 442 * Currently used for modulesTree, which allows quick decisions on whether a 443 * module exists. 444 */ 445 void preCache() 446 { 447 foreach (name; modules.byKey) 448 { 449 auto parts = name.splitter("."); 450 MembersTree* node = &modulesTree; 451 foreach (part; parts) 452 { 453 node.type = SymbolType.Package; 454 MembersTree* child = part in node.children; 455 if (child is null) 456 { 457 node.children[part] = MembersTree.init; 458 child = part in node.children; 459 } 460 node = child; 461 } 462 // The leaf nodes of the module tree are packages. 463 node.type = SymbolType.Module; 464 } 465 } 466 467 /// Member trees of all modules, indexed by full module names. 468 MembersTree[string] modules; 469 470 /// Allows to quickly determine whether a module exists. Built by preCache. 471 MembersTree modulesTree; 472 473 /// Get members of symbol with specified name stack in specified module. 474 MembersTree* getMembers(string moduleName, string[] symbolStack) 475 { 476 MembersTree* members = &modules[moduleName]; 477 symbolStack.each!(namePart => members = &members.children[namePart]); 478 return members; 479 } 480 } 481 482 /// Enumberates types of symbols in the symbol database. 483 enum SymbolType : ubyte 484 { 485 /// A package with no module file (package.d would be considered a module). 486 Package, 487 /// A module. 488 Module, 489 /// An alias. 490 Alias, 491 /// An enum. 492 Enum, 493 /// A class. 494 Class, 495 /// A struct. 496 Struct, 497 /// An interface. 498 Interface, 499 /// A function (including e.g. constructors). 500 Function, 501 /// A template (not a template function/template class/etc). 502 Template, 503 /// Only used for enum members at the moment. 504 Value, 505 /// A variable member. 506 Variable 507 } 508 509 /// Data we keep track of for a module. 510 struct SymbolDataModule 511 { 512 /// Summary comment of the module, *not* processes by Markdown. 513 string summary; 514 } 515 516 private: 517 518 // Reusing Members here is a very quick hack, and we may need something better than a 519 // tree of AA's if generating docs for big projects is too slow. 520 /// Recursive tree of all members of a symbol. 521 struct MembersTree 522 { 523 /// Members of children of this tree node. 524 MembersTree[string] children; 525 526 /// Type of this symbol. 527 SymbolType type; 528 529 union 530 { 531 /// Data specific for a module symbol. 532 SymbolDataModule dataModule; 533 //TODO data for any other symbol types. In a union to save space. 534 } 535 } 536 537 538 /** Gather data about symbols in a module into a SymbolDatabase. 539 * 540 * Writes directly into the passed SymbolDatabase. 541 * 542 * Params: 543 * 544 * config = harbored-mod configuration. 545 * database = SymbolDatabase to gather data into. 546 * writer = Writer (e.g. HTMLWriter), used to determine links for symbols (as Writer 547 * decides where to put symbols). 548 * modulePath = Path of the module file. 549 */ 550 void gatherModuleData(Writer) 551 (ref const(Config) config, SymbolDatabase database, Writer writer, string modulePath) 552 { 553 // Load the module file. 554 import std.file : readText, FileException; 555 ubyte[] fileBytes; 556 try 557 fileBytes = cast(ubyte[]) modulePath.readText!(char[]); 558 catch (FileException e) 559 { 560 writefln("Failed to load file %s: will be ignored", modulePath); 561 return; 562 } 563 564 // Parse the module. 565 LexerConfig lexConfig; 566 lexConfig.fileName = modulePath; 567 lexConfig.stringBehavior = StringBehavior.source; 568 import main : ignoreParserError; 569 import dparse.rollback_allocator : RollbackAllocator; 570 571 RollbackAllocator allocator; 572 Module m = fileBytes.getTokensForParser(lexConfig, &database.cache) 573 .parseModule(modulePath, &allocator, &ignoreParserError); 574 575 // Gather data. 576 auto visitor = new DataGatherVisitor!Writer(config, database, writer, modulePath); 577 visitor.visit(m); 578 } 579 580 /** Visits AST nodes to gather data about symbols in a module. 581 */ 582 class DataGatherVisitor(Writer) : ASTVisitor 583 { 584 /** Construct a DataGatherVisitor. 585 * Params: 586 * 587 * config = Configuration data, including macros and the output directory. 588 * database = Database to gather data into. 589 * writer = Used to determine link strings. 590 * fileName = Module file name. 591 */ 592 this(ref const Config config, SymbolDatabase database, Writer writer, string fileName) 593 { 594 this.config = &config; 595 this.database = database; 596 this.writer = writer; 597 this.fileName = fileName; 598 } 599 600 alias visit = ASTVisitor.visit; 601 602 /// Determines module name and adds a MemberTree for it to the database. 603 override void visit(const Module mod) 604 { 605 import std.range : chain, iota, join, only; 606 import std.conv : to; 607 608 if (mod.moduleDeclaration is null) 609 { 610 writefln("Ignoring file %s: no 'module' declaration", fileName); 611 return; 612 } 613 auto stack = cast(string[])mod.moduleDeclaration.moduleName.identifiers.map!(a => a.text).array; 614 615 foreach (exclude; config.excludes) 616 { 617 // If module name is pkg1.pkg2.mod, we first check 618 // "pkg1", then "pkg1.pkg2", then "pkg1.pkg2.mod" 619 // i.e. we only check for full package/module names. 620 if (iota(stack.length + 1).map!(l => stack[0 .. l].join(".")).canFind(exclude)) 621 { 622 writeln("Excluded module ", stack.join(".")); 623 return; 624 } 625 } 626 627 moduleName = stack.join(".").to!string; 628 database.moduleNames ~= moduleName; 629 database.moduleFiles ~= fileName; 630 database.moduleNameToLink[moduleName] = writer.moduleLink(stack); 631 database.modules[moduleName] = MembersTree.init; 632 633 database.modules[moduleName].type = SymbolType.Module; 634 database.modules[moduleName].dataModule.summary = 635 commentSummary(mod.moduleDeclaration.comment); 636 637 mod.accept(this); 638 } 639 640 /// Gather data about various members /// 641 642 override void visit(const EnumDeclaration ed) 643 { 644 visitAggregateDeclaration!(SymbolType.Enum)(ed); 645 } 646 647 // Document all enum members even if they have no doc comments. 648 override void visit(const EnumMember member) 649 { 650 // Link to the enum owning the member (enum members themselves have no 651 // files/detailed explanations). 652 pushSymbol(member.name.text, SymbolType.Value); 653 scope(exit) popSymbol(); 654 } 655 656 override void visit(const ClassDeclaration cd) 657 { 658 visitAggregateDeclaration!(SymbolType.Class)(cd); 659 } 660 661 override void visit(const TemplateDeclaration td) 662 { 663 visitAggregateDeclaration!(SymbolType.Template)(td); 664 } 665 666 override void visit(const StructDeclaration sd) 667 { 668 visitAggregateDeclaration!(SymbolType.Struct)(sd); 669 } 670 671 override void visit(const InterfaceDeclaration id) 672 { 673 visitAggregateDeclaration!(SymbolType.Interface)(id); 674 } 675 676 override void visit(const AliasDeclaration ad) 677 { 678 if (ad.comment is null) 679 return; 680 681 if (ad.declaratorIdentifierList !is null) 682 foreach (name; ad.declaratorIdentifierList.identifiers) 683 { 684 pushSymbol(name.text, SymbolType.Alias); 685 scope(exit) popSymbol(); 686 } 687 else foreach (initializer; ad.initializers) 688 { 689 pushSymbol(initializer.name.text, SymbolType.Alias); 690 scope(exit) popSymbol(); 691 } 692 } 693 694 override void visit(const VariableDeclaration vd) 695 { 696 foreach (const Declarator dec; vd.declarators) 697 { 698 if (vd.comment is null && dec.comment is null) 699 continue; 700 pushSymbol(dec.name.text, SymbolType.Variable); 701 scope(exit) popSymbol(); 702 } 703 if (vd.comment !is null && vd.autoDeclaration !is null) 704 { 705 foreach (part; vd.autoDeclaration.parts) with (part) 706 { 707 pushSymbol(identifier.text, SymbolType.Variable); 708 scope(exit) popSymbol(); 709 string[] storageClasses; 710 vd.storageClasses.each!(stor => storageClasses ~= str(stor.token.type)); 711 } 712 } 713 } 714 715 override void visit(const Constructor cons) 716 { 717 if (cons.comment is null) 718 return; 719 visitFunctionDeclaration("this", cons); 720 } 721 722 override void visit(const FunctionDeclaration fd) 723 { 724 if (fd.comment is null) 725 return; 726 visitFunctionDeclaration(fd.name.text, fd); 727 } 728 729 // Optimization: don't allow visit() for these AST nodes to result in visit() 730 // calls for their subnodes. This avoids most of the dynamic cast overhead. 731 override void visit(const AssignExpression assignExpression) {} 732 override void visit(const CmpExpression cmpExpression) {} 733 override void visit(const TernaryExpression ternaryExpression) {} 734 override void visit(const IdentityExpression identityExpression) {} 735 override void visit(const InExpression inExpression) {} 736 737 private: 738 /** If the comment starts with a summary, return it, otherwise return null. 739 * 740 * Note: as libdparse does not seem to recognize summaries correctly (?), 741 * we simply assume the first section of the comment to be the summary. 742 */ 743 string commentSummary(string comment) 744 { 745 if (comment.empty) 746 return null; 747 748 import core.exception: RangeError; 749 try 750 { 751 import ddoc.comments : Comment, parseComment; 752 auto app = appender!string(); 753 if (comment.length >= 3) 754 comment.unDecorateComment(app); 755 756 Comment c = parseComment(app.data, cast(string[string])config.macros); 757 if (c.sections.length) 758 return c.sections[0].content; 759 } 760 catch (RangeError e) 761 { 762 writeln("RangeError"); 763 // Writer.readAndWriteComment will catch this too and 764 // write an error message. Not kosher to catch Errors 765 // but unfortunately needed with libdparse ATM (2015). 766 return null; 767 } 768 return null; 769 } 770 771 void visitAggregateDeclaration(SymbolType type, A)(const A ad) 772 { 773 if (ad.comment is null) 774 return; 775 776 // pushSymbol will push to stack, add tree entry and return MembersTree 777 // containing that entry so we can also add the aggregate to the correct 778 // Item array 779 pushSymbol(ad.name.text, type); 780 scope(exit) popSymbol(); 781 782 ad.accept(this); 783 } 784 785 void visitFunctionDeclaration(Fn)(string name, Fn fn) 786 { 787 pushSymbol(name, SymbolType.Function); 788 scope(exit) popSymbol(); 789 790 string fdName; 791 static if (__traits(hasMember, typeof(fn), "name")) 792 fdName = fn.name.text; 793 else 794 fdName = "this"; 795 fn.accept(this); 796 } 797 798 /** Push a symbol to the stack, moving into its scope. 799 * 800 * Params: 801 * 802 * name = The symbol's name 803 * type = Type of the symbol. 804 * 805 * Returns: Tree of the *parent* symbol of the pushed symbol. 806 */ 807 MembersTree* pushSymbol(string name, SymbolType type) 808 { 809 auto parentStack = symbolStack; 810 symbolStack ~= name; 811 812 MembersTree* members = database.getMembers(moduleName, parentStack); 813 if(!(name in members.children)) 814 { 815 members.children[name] = MembersTree.init; 816 members.children[name].type = type; 817 } 818 return members; 819 } 820 821 /// Leave scope of a symbol, moving back to its parent. 822 void popSymbol() 823 { 824 symbolStack.popBack(); 825 } 826 827 /// Harbored-mod configuration. 828 const(Config)* config; 829 /// Database we're gathering data into. 830 SymbolDatabase database; 831 /// Used to determine links to symbols. 832 Writer writer; 833 /// Filename of this module. 834 string fileName; 835 /// Name of this module in D code. 836 string moduleName; 837 /** Namespace stack of the current symbol, without the package/module name. 838 * 839 * E.g. ["Class", "member"] 840 */ 841 string[] symbolStack; 842 }