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 }