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 }