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 }