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