1 /**
2 	Parses DMD JSON output and builds up a documentation syntax tree (JSON format from DMD 2.063.2).
3 
4 	Copyright: © 2012-2016 RejectedSoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig
7 */
8 module ddox.parsers.jsonparser;
9 
10 import ddox.ddox;
11 import ddox.entities;
12 
13 import std.algorithm;
14 import std.conv;
15 import std.exception;
16 import std.range;
17 import std.stdio;
18 import std.string;
19 import std.typecons;
20 import core.demangle;
21 import vibe.core.log;
22 import vibe.data.json;
23 
24 
25 Package parseJsonDocs(Json json, Package root = null)
26 {
27 	if( !root ) root = new Package(null, null);
28 	Parser p;
29 	foreach (mod; json)
30 		p.parseModuleDecls(mod, root);
31 	p.parseTypes();
32 	return root;
33 }
34 
35 private struct Parser
36 {
37 	// global map of type declarations with all partially qualified names
38 	// used to lookup type names for which the regular lookup has failed
39 	private Declaration[string] m_typeMap;
40 
41 	Tuple!(Declaration, Json)[] m_declarations;
42 
43 	void parseModuleDecls(Json json, Package root_package)
44 	{
45 		Module mod;
46 		if( "name" !in json ){
47 			logError("No name attribute in module %s - ignoring", json["filename"].opt!string);
48 			return;
49 		}
50 		auto path = json["name"].get!string.split(".");
51 		Package p = root_package;
52 		foreach (i, pe; path) {
53 			if( i+1 < path.length ) p = p.getOrAddPackage(pe);
54 			else mod = p.createModule(pe);
55 		}
56 
57 		mod.file = json["file"].get!string;
58 		mod.docGroup = new DocGroup(mod, json["comment"].opt!string());
59 		mod.members = parseDeclList(json["members"], mod);
60 	}
61 
62 	void parseTypes()
63 	{
64 		foreach (d; m_declarations) {
65 			auto decl = d[0];
66 			auto json = d[1];
67 			final switch (decl.kind) {
68 				case DeclarationKind.Variable: {
69 						auto v = cast(VariableDeclaration)decl;
70 						v.type = parseType(json, v);
71 					} break;
72 				case DeclarationKind.Function: {
73 						auto f = cast(FunctionDeclaration)decl;
74 						f.type = parseType(json, f, "void()");
75 						if (f.type.kind != TypeKind.Function) {
76 							logError("Function %s has non-function type: %s", f.qualifiedName, f.type.kind);
77 							break;
78 						}
79 						f.returnType = f.type.returnType;
80 						f.attributes ~= f.type.attributes ~ f.type.modifiers;
81 
82 						auto params = json["parameters"].opt!(Json[]);
83 						if (!params) {
84 							params.length = f.type.parameterTypes.length;
85 							foreach (i, pt; f.type.parameterTypes) {
86 								auto jp = Json.emptyObject;
87 								jp["name"] = f.type._parameterNames[i];
88 								jp["type"] = pt.text;
89 								if (f.type._parameterDefaultValues[i])
90 									jp["default"] = f.type._parameterDefaultValues[i].valueString;
91 								params[i] = jp;
92 							}
93 						}
94 
95 						f.parameters.reserve(params.length);
96 						foreach (i, p; params) {
97 							auto pname = p["name"].opt!string;
98 							auto pdecl = new VariableDeclaration(f, pname);
99 							pdecl.type = parseType(p, f);
100 							foreach (sc; p["storageClass"].opt!(Json[]))
101 								if (!pdecl.attributes.canFind(sc.get!string))
102 									pdecl.attributes ~= CachedString(sc.get!string);
103 							if (auto pdv = "default" in p)
104 								pdecl.initializer = parseValue(pdv.opt!string);
105 							f.parameters ~= pdecl;
106 						}
107 					} break;
108 				case DeclarationKind.Struct: break;
109 				case DeclarationKind.Union: break;
110 				case DeclarationKind.Class: {
111 						auto c = cast(ClassDeclaration)decl;
112 						if (!c.qualifiedName.equal("object.Object")) {
113 							c.baseClass = parseType(json["base"], c, "Object", false);
114 						}
115 						foreach (intf; json["interfaces"].opt!(Json[]))
116 							c.derivedInterfaces ~= CachedType(parseType(intf, c));
117 					} break;
118 				case DeclarationKind.Interface: {
119 						auto i = cast(InterfaceDeclaration)decl;
120 						foreach (intf; json["interfaces"].opt!(Json[]))
121 							i.derivedInterfaces ~= CachedType(parseType(intf, i));
122 					} break;
123 				case DeclarationKind.Enum: {
124 						auto e = cast(EnumDeclaration)decl;
125 						e.baseType = parseType(json["base"], e);
126 					} break;
127 				case DeclarationKind.EnumMember: break;
128 				case DeclarationKind.Alias: {
129 						auto a = cast(AliasDeclaration)decl;
130 						a.targetType = parseType(json, a, null);
131 					} break;
132 				case DeclarationKind.Template: break;
133 				case DeclarationKind.TemplateParameter:
134 					auto tp = cast(TemplateParameterDeclaration)decl;
135 					if (json["kind"] == "value")
136 						tp.type = parseType(json, tp, null);
137 					break;
138 			}
139 		}
140 	}
141 
142 	Declaration[] parseDeclList(Json json, Entity parent)
143 	{
144 		if( json.type == Json.Type.Undefined ) return null;
145 		DocGroup lastdoc;
146 		Declaration[] ret;
147 		foreach( mem; json ){
148 			auto decl = parseDecl(mem, parent);
149 			if( !decl ) continue;
150 			auto doc = decl.docGroup;
151 			if( lastdoc && (doc.text == lastdoc.text && doc.text.length || doc.comment.isDitto) ){
152 				lastdoc.members ~= decl;
153 				decl.docGroup = lastdoc;
154 			} else if( doc.comment.isPrivate ){
155 				decl.protection = Protection.Private;
156 				lastdoc = null;
157 			} else lastdoc = decl.docGroup;
158 			ret ~= decl;
159 		}
160 		return ret;
161 	}
162 
163 	Declaration parseDecl(Json json, Entity parent)
164 	{
165 		Declaration ret;
166 
167 		// DMD outputs templates with the wrong kind sometimes
168 		if (json["name"].get!string().canFind('(') && json["kind"] != "mixin") {
169 			ret = parseTemplateDecl(json, parent);
170 		} else {
171 			switch( json["kind"].get!string ){
172 				default:
173 					logWarn("Unknown declaration kind: %s", json["kind"].get!string);
174 					return null;
175 				case "generated function": // generated functions are never documented
176 					return null;
177 				case "import":
178 				case "static import":
179 					// TODO: use for symbol resolving
180 					return null;
181 				case "destructor": return null;
182 				case "mixin": return null;
183 				case "alias":
184 					ret = parseAliasDecl(json, parent);
185 					break;
186 				case "function":
187 				case "allocator":
188 				case "deallocator":
189 				case "constructor":
190 					ret = parseFunctionDecl(json, parent);
191 					break;
192 				case "enum":
193 					ret = parseEnumDecl(json, parent);
194 					break;
195 				case "enum member":
196 					ret = parseEnumMemberDecl(json, parent);
197 					break;
198 				case "struct":
199 				case "union":
200 				case "class":
201 				case "interface":
202 					ret = parseCompositeDecl(json, parent);
203 					break;
204 				case "variable":
205 					ret = parseVariableDecl(json, parent);
206 					break;
207 				case "template":
208 					ret = parseTemplateDecl(json, parent);
209 					break;
210 			}
211 		}
212 
213 		ret.protection = parseProtection(json["protection"]);
214 		ret.line = json["line"].opt!int;
215 		ret.docGroup = new DocGroup(ret, json["comment"].opt!string());
216 
217 		m_declarations ~= tuple(ret, json);
218 
219 		return ret;
220 	}
221 
222 	auto parseAliasDecl(Json json, Entity parent)
223 	{
224 		auto ret = new AliasDeclaration(parent, json["name"].get!string);
225 		ret.attributes = json["storageClass"].opt!(Json[]).map!(j => CachedString(j.get!string)).array.assumeUnique;
226 		if( ret.targetType && ret.targetType.kind == TypeKind.Primitive && ret.targetType.typeName.length == 0 )
227 			ret.targetType = CachedType.init;
228 		insertIntoTypeMap(ret);
229 		return ret;
230 	}
231 
232 	auto parseFunctionDecl(Json json, Entity parent)
233 	{
234 		auto ret = new FunctionDeclaration(parent, json["name"].opt!string);
235 		if (auto psc = "storageClass" in json)
236 			foreach (sc; *psc)
237 				if (!ret.attributes.canFind(sc.get!string))
238 					ret.attributes ~= CachedString(sc.get!string);
239 		return ret;
240 	}
241 
242 	auto parseEnumDecl(Json json, Entity parent)
243 	{
244 		auto ret = new EnumDeclaration(parent, json["name"].get!string);
245 		insertIntoTypeMap(ret);
246 		if( "base" !in json ){ // FIXME: parse deco instead
247 			if( auto pd = "baseDeco" in json )
248 				json["base"] = demanglePrettyType(pd.get!string());
249 		}
250 		auto mems = parseDeclList(json["members"], ret);
251 		foreach( m; mems ){
252 			auto em = cast(EnumMemberDeclaration)m;
253 			assert(em !is null, "Enum containing non-enum-members?");
254 			ret.members ~= em;
255 		}
256 		return ret;
257 	}
258 
259 	auto parseEnumMemberDecl(Json json, Entity parent)
260 	{
261 		auto ret = new EnumMemberDeclaration(parent, json["name"].get!string);
262 		if (json["value"].opt!string.length)
263 			ret.value = parseValue(json["value"].opt!string);
264 		return ret;
265 	}
266 
267 	auto parseCompositeDecl(Json json, Entity parent)
268 	{
269 		CompositeTypeDeclaration ret;
270 		switch(json["kind"].get!string){
271 			default:
272 				logWarn("Invalid composite decl kind: %s", json["kind"].get!string);
273 				return new StructDeclaration(parent, json["name"].get!string);
274 			case "struct":
275 				ret = new StructDeclaration(parent, json["name"].get!string);
276 				break;
277 			case "union":
278 				ret = new UnionDeclaration(parent, json["name"].get!string);
279 				break;
280 			case "class":
281 				auto clsdecl = new ClassDeclaration(parent, json["name"].get!string);
282 				ret = clsdecl;
283 				break;
284 			case "interface":
285 				auto intfdecl = new InterfaceDeclaration(parent, json["name"].get!string);
286 				ret = intfdecl;
287 				break;
288 		}
289 
290 		insertIntoTypeMap(ret);
291 
292 		ret.members = parseDeclList(json["members"], ret);
293 
294 		return ret;
295 	}
296 
297 	Declaration parseVariableDecl(Json json, Entity parent)
298 	{
299 		if (json["storageClass"].opt!(Json[]).canFind!(j => j.opt!string == "enum")) {
300 			auto ret = new EnumMemberDeclaration(parent, json["name"].get!string);
301 			if (json["init"].opt!string.length)
302 				ret.value = parseValue(json["init"].opt!string);
303 			return ret;
304 		} else {
305 			auto ret = new VariableDeclaration(parent, json["name"].get!string);
306 			if (json["init"].opt!string.length)
307 				ret.initializer = parseValue(json["init"].opt!string);
308 			return ret;
309 		}
310 	}
311 
312 	auto parseTemplateDecl(Json json, Entity parent)
313 	{
314 		auto ret = new TemplateDeclaration(parent, json["name"].get!string);
315 		foreach (arg; json["parameters"].opt!(Json[])) {
316 			string pname = arg["name"].get!string;
317 			string defvalue = arg["defaultValue"].opt!string;
318 			bool needs_type_parse = false;
319 
320 			switch (arg["kind"].get!string) {
321 				default: break;
322 				case "value":
323 					needs_type_parse = true;
324 					break;
325 				case "alias":
326 					pname = "alias " ~ pname;
327 					break;
328 				case "tuple":
329 					pname ~= "...";
330 					break;
331 			}
332 
333 			auto pdecl = new TemplateParameterDeclaration(ret, pname);
334 			pdecl.defaultValue = defvalue;
335 			ret.templateArgs ~= pdecl;
336 			ret.templateConstraint = json["constraint"].opt!string;
337 
338 			if (needs_type_parse)
339 				m_declarations ~= tuple(cast(Declaration)pdecl, arg);
340 		}
341 		ret.members = parseDeclList(json["members"], ret);
342 		return ret;
343 	}
344 
345 	Type parseType(Json json, Entity sc, string def_type = "void", bool warn_if_not_exists = true)
346 		out(ret) { assert(!def_type.length || ret != Type.init); }
347 	body {
348 		string str;
349 		if( json.type == Json.Type.Undefined ){
350 			if (warn_if_not_exists) logWarn("No type found for %s.", sc.qualifiedName);
351 			str = def_type;
352 		} else if (json.type == Json.Type.String) str = json.get!string();
353 		else if (auto pv = "deco" in json) str = demanglePrettyType(pv.get!string());
354 		else if (auto pv = "type" in json) str = fixFunctionType(pv.get!string(), def_type);
355 		else if (auto pv = "originalType" in json) str = fixFunctionType(pv.get!string(), def_type);
356 
357 		if( str.length == 0 ) str = def_type;
358 
359 		if( !str.length ) return Type.init;
360 
361 		return parseType(str, sc);
362 	}
363 
364 	Type parseType(string str, Entity sc)
365 		out(ret) { assert(ret != Type.init); }
366 	body {
367 		auto tokens = tokenizeDSource(str);
368 
369 		logDebug("parse type '%s'", str);
370 		try {
371 			auto type = parseTypeDecl(tokens, sc);
372 			type.text = str;
373 			return type;
374 		} catch( Exception e ){
375 			logError("Error parsing type '%s': %s", str, e.msg);
376 			Type type;
377 			type.text = str;
378 			type.typeName = str;
379 			type.kind = TypeKind.Primitive;
380 			return type;
381 		}
382 	}
383 
384 	Value parseValue(string str)
385 	{
386 		auto ret = new Value;
387 		//ret.type = ;
388 		ret.valueString = str;
389 		return ret;
390 	}
391 
392 	Protection parseProtection(Json prot)
393 	{
394 		switch( prot.opt!string ){
395 			default: return Protection.Public;
396 			case "package": return Protection.Package;
397 			case "protected": return Protection.Protected;
398 			case "private": return Protection.Private;
399 		}
400 	}
401 
402 	Type parseTypeDecl(ref string[] tokens, Entity sc)
403 	{
404 
405 		auto ret = parseType(tokens, sc);
406 		return ret;
407 	}
408 
409 	Type parseType(ref string[] tokens, Entity sc)
410 	{
411 		CachedString[] attributes;
412 		auto basic_type = parseBasicType(tokens, sc, attributes);
413 		basic_type.attributes ~= attributes;
414 		return basic_type;
415 	}
416 
417 	Type parseBasicType(ref string[] tokens, Entity sc, out CachedString[] attributes)
418 		out(ret) { assert(ret != Type.init); }
419 	body {
420 		static immutable global_attribute_keywords = ["abstract", "auto", "const", "deprecated", "enum",
421 			"extern", "final", "immutable", "inout", "shared", "nothrow", "override", "pure",
422 			"__gshared", "scope", "static", "synchronize"];
423 
424 		static immutable parameter_attribute_keywords = ["auto", "const", "final", "immutable", "in", "inout",
425 			"lazy", "out", "ref", "return", "scope", "shared"];
426 
427 		static immutable member_function_attribute_keywords = ["const", "immutable", "inout", "ref", "return",
428 			"scope", "shared", "pure", "nothrow"];
429 
430 
431 		if( tokens.length > 0 && tokens[0] == "extern" ){
432 			enforce(tokens[1] == "(");
433 			enforce(tokens[3] == ")");
434 			attributes ~= CachedString(join(tokens[0 .. 4]));
435 			tokens = tokens[4 .. $];
436 		}
437 
438 		static immutable string[] attribute_keywords = global_attribute_keywords ~ parameter_attribute_keywords ~ member_function_attribute_keywords;
439 		/*final switch( sc ){
440 			case DeclScope.Global: attribute_keywords = global_attribute_keywords; break;
441 			case DeclScope.Parameter: attribute_keywords = parameter_attribute_keywords; break;
442 			case DeclScope.Class: attribute_keywords = member_function_attribute_keywords; break;
443 		}*/
444 
445 		void parseAttributes(const(string)[] keywords, scope void delegate(CachedString s) del)
446 		{
447 			while( tokens.length > 0 ){
448 				if( tokens.front == "@" ){
449 					tokens.popFront();
450 					del(CachedString("@"~tokens.front));
451 					tokens.popFront();
452 				} else if( keywords.countUntil(tokens[0]) >= 0 && tokens.length > 1 && tokens[1] != "(" ){
453 					del(CachedString(tokens.front));
454 					tokens.popFront();
455 				} else break;
456 			}
457 		}
458 
459 		parseAttributes(attribute_keywords, (k) { attributes ~= k; });
460 
461 
462 		Type type;
463 		static immutable const_modifiers = ["const", "immutable", "shared", "inout"];
464 		if (tokens.length > 2 && tokens[1] == "(" && const_modifiers.countUntil(tokens[0]) >= 0) {
465 			auto mod = tokens.front;
466 			tokens.popFrontN(2);
467 			CachedString[] subattrs;
468 			type = parseBasicType(tokens, sc, subattrs);
469 			type.modifiers ~= CachedString(mod);
470 			type.attributes ~= subattrs;
471 			enforce(!tokens.empty && tokens.front == ")", format("Missing ')' for '%s('", mod));
472 			tokens.popFront();
473 		} else if (!tokens.empty && !tokens.front.among("function", "delegate")) {
474 			type.kind = TypeKind.Primitive;
475 
476 			size_t start = 0, end;
477 			if( tokens[start] == "." ) start++;
478 			for( end = start; end < tokens.length && isIdent(tokens[end]); ){
479 				end++;
480 				if( end >= tokens.length || tokens[end] != "." )
481 					break;
482 				end++;
483 			}
484 
485 			size_t i = end;
486 
487 			string type_name, nested_name;
488 			if( i == 0 && tokens[0] == "(" ){
489 				type_name = "constructor";
490 				nested_name = null;
491 			} else {
492 				enforce(i > 0, "Expected identifier but got "~tokens.front);
493 				auto unqualified_name = tokens[end - 1];
494 				type.typeName = join(tokens[start .. end]);
495 				//
496 				tokens.popFrontN(i);
497 
498 				if (type.typeName == "typeof" && !tokens.empty && tokens.front == "(") {
499 					type.typeName ~= "(";
500 					tokens.popFront();
501 					int level = 1;
502 					while (!tokens.empty && level > 0) {
503 						if (tokens.front == "(") level++;
504 						else if( tokens.front == ")") level--;
505 						type.typeName ~= tokens.front;
506 						tokens.popFront();
507 					}
508 				} else if( !tokens.empty && tokens.front == "!" ){
509 					tokens.popFront();
510 					if( tokens.front == "(" ){
511 						size_t j = 1;
512 						int cc = 1;
513 						while( cc > 0 ){
514 							assert(j < tokens.length);
515 							if( tokens[j] == "(" ) cc++;
516 							else if( tokens[j] == ")") cc--;
517 							j++;
518 						}
519 						type.templateArgs = join(tokens[0 .. j]);
520 						tokens.popFrontN(j);
521 						logDebug("templargs: %s", type.templateArgs);
522 					} else {
523 						type.templateArgs = tokens[0];
524 						tokens.popFront();
525 					}
526 
527 					// resolve eponymous template member, e.g. test.Foo!int.Foo
528 					if (!tokens.empty && tokens.front == ".") {
529 						tokens.popFront();
530 						if (!tokens.empty && tokens.front == unqualified_name) { // eponymous template
531 							resolveTypeDecl(type, sc);
532 							auto tdecl = cast(TemplateDeclaration)type.typeDecl;
533 							auto members = tdecl ? tdecl.members : null;
534 							auto mi = members.countUntil!(m => m.name == tokens.front);
535 							assert(mi >= 0 || members.empty);
536 							if (mi >= 0)
537 								type.typeDecl = members[mi];
538 							tokens.popFront();
539 						}
540 					}
541 					// HACK: dropping the actual type name here!
542 					// TODO: resolve other members and adjust typeName,
543 					// e.g. test.Foo!int.Enum, test.Foo!int.Bar!int, test.Foo!int.Struct.Templ!(int, double)
544 					while (!tokens.empty && tokens.front == ".") {
545  						tokens.popFront();
546 						if (!tokens.empty()) tokens.popFront();
547 					}
548 				}
549 
550 				resolveTypeDecl(type, sc);
551 			}
552 		}
553 
554 		while( !tokens.empty ){
555 			if( tokens.front == "*" ){
556 				Type ptr;
557 				ptr.kind = TypeKind.Pointer;
558 				ptr.elementType = type;
559 				type = ptr;
560 				tokens.popFront();
561 			} else if( tokens.front == "[" ){
562 				tokens.popFront();
563 				enforce(!tokens.empty, "Missing ']'.");
564 				if( tokens.front == "]" ){
565 					Type arr;
566 					arr.kind = TypeKind.Array;
567 					arr.elementType = type;
568 					type = arr;
569 				} else {
570 					string[] tokens_copy = tokens;
571 					Type keytp;
572 					if (!isDigit(tokens.front[0]) && tokens.front != "!") keytp = parseType(tokens_copy, sc);
573 					if (keytp != Type.init && !tokens_copy.empty && tokens_copy.front == "]") {
574 						tokens = tokens_copy;
575 						Type aa;
576 						aa.kind = TypeKind.AssociativeArray;
577 						aa.elementType = type;
578 						aa.keyType = keytp;
579 						type = aa;
580 					} else {
581 						Type arr;
582 						arr.kind = TypeKind.StaticArray;
583 						arr.elementType = type;
584 						arr.arrayLength = tokens.front;
585 						tokens.popFront();
586 						while (!tokens.empty && tokens.front != "]") {
587 							arr.arrayLength ~= tokens.front;
588 							tokens.popFront();
589 						}
590 						type = arr;
591 					}
592 				}
593 				enforce(tokens.front == "]", "Expected ']', got '"~tokens.front~"'.");
594 				tokens.popFront();
595 			} else break;
596 		}
597 
598 		if (type == Type.init) {
599 			type.kind = TypeKind.Primitive;
600 			type.typeName = "auto";
601 		}
602 
603 		while (!tokens.empty && (tokens.front == "function" || tokens.front == "delegate" || tokens.front == "(")) {
604 			Type ftype;
605 			ftype.kind = tokens.front == "(" || tokens.front == "function" ? TypeKind.Function : TypeKind.Delegate;
606 			ftype.returnType = type;
607 			if (tokens.front != "(") tokens.popFront();
608 			enforce(tokens.front == "(");
609 			tokens.popFront();
610 			// demangleType() returns something like "void(, ...)" for variadic functions or "void(, type)" for typeof(null) parameters
611 			if (!tokens.empty && tokens.front == ",") tokens.popFront();
612 			// (...) - D variadic function
613 			if (tokens.front == "...") {
614 				ftype.variadic = Type.Variadic.d;
615 				tokens.popFront();
616 			}
617 			while (true) {
618 				if (tokens.front == ")") break;
619 
620 				// (int) - parameter type
621 				enforce(!tokens.empty);
622 				ftype.parameterTypes ~= CachedType(parseTypeDecl(tokens, sc));
623 
624 
625 				// (int[]...), (Clazz...) - typesafe variadic function
626 				if (tokens.front == "...") {
627 					ftype.variadic = Type.Variadic.typesafe;
628 					tokens.popFront();
629 				}
630 				// (type, ...) - D or extern(C) variadic
631 				else if (tokens.length > 2 && tokens[0] == "," && tokens[1] == "...") {
632 					ftype.variadic = Type.Variadic.c; // c and d treated identical for doc-gen
633 					tokens.popFrontN(2);
634 				}
635 
636 				string pname;
637 				if (tokens.front != "," && tokens.front != ")") {
638 					pname = tokens.front;
639 					tokens.popFront();
640 				}
641 				ftype._parameterNames ~= CachedString(pname);
642 				if (tokens.front == "=") {
643 					tokens.popFront();
644 					string defval;
645 					int ccount = 0;
646 					while (!tokens.empty) {
647 						if (ccount == 0 && (tokens.front == "," || tokens.front == ")"))
648 							break;
649 						if (tokens.front == "(") ccount++;
650 						else if (tokens.front == ")") ccount--;
651 						defval ~= tokens.front;
652 						tokens.popFront();
653 					}
654 					ftype._parameterDefaultValues ~= cast(immutable)parseValue(defval);
655 					logDebug("got defval %s", defval);
656 				} else ftype._parameterDefaultValues ~= null;
657 				if (tokens.front == ")") break;
658 				enforce(tokens.front == ",", "Expecting ',', got "~tokens.front);
659 				tokens.popFront();
660 			}
661 			tokens.popFront();
662 
663 			parseAttributes(member_function_attribute_keywords, (k) { ftype.attributes ~= cast(immutable)k; });
664 
665 			type = ftype;
666 		}
667 
668 		return type;
669 	}
670 
671 	/* special function that looks at the default type to see if a function type
672 		is expected and if that's the case, fixes up the type string to read
673 		as a valid D type declaration (DMD omits "function"/"delegate", which
674 		results in an ambiguous meaning)
675 	*/
676 	private string fixFunctionType(string type, string deftype)
677 	{
678 		Type dt = parseType(deftype, new Module(null, "dummy"));
679 		if (deftype == "void()" || dt != Type.init && dt.kind.among(TypeKind.Function, TypeKind.Delegate)) {
680 			auto last_clamp = type.lastIndexOf(')');
681 			auto idx = last_clamp-1;
682 			int l = 1;
683 			while (idx >= 0) {
684 				if (type[idx] == ')') l++;
685 				else if (type[idx] == '(') l--;
686 				if (l == 0) break;
687 				idx--;
688 			}
689 			if (idx <= 0 || l > 0) return type;
690 			return type[0 .. idx] ~ " function" ~ type[idx .. $];
691 		}
692 		return type;
693 	}
694 
695 	string[] tokenizeDSource(string dsource)
696 	{
697 		static import std.uni;
698 		import std.utf : stride;
699 
700 		static immutable string[] tokens = [
701 			"/", "/=", ".", "..", "...", "&", "&=", "&&", "|", "|=", "||",
702 			"-", "-=", "--", "+", "+=", "++", "<", "<=", "<<", "<<=",
703 			"<>", "<>=", ">", ">=", ">>=", ">>>=", ">>", ">>>", "!", "!=",
704 			"!<>", "!<>=", "!<", "!<=", "!>", "!>=", "(", ")", "[", "]",
705 			"{", "}", "?", ",", ";", ":", "$", "=", "==", "*", "*=",
706 			"%", "%=", "^", "^=", "~", "~=", "@", "=>", "#", "C++"
707 		];
708 		static bool[string] token_map;
709 
710 		if (token_map is null) {
711 			foreach (t; tokens)
712 				token_map[t] = true;
713 			token_map.rehash;
714 		}
715 
716 		string[] ret;
717 		outer:
718 		while(true){
719 			dsource = stripLeft(dsource);
720 			if( dsource.length == 0 ) break;
721 
722 			// special token?
723 			foreach_reverse (i; 1 .. min(5, dsource.length+1))
724 				if (dsource[0 .. i] in token_map) {
725 					ret ~= dsource[0 .. i];
726 					dsource = dsource[i .. $];
727 					continue outer;
728 				}
729 
730 			// identifier?
731 			if( dsource[0] == '_' || std.uni.isAlpha(dsource.front) ){
732 				size_t i = 1;
733 				string rem = dsource;
734 				rem.popFront();
735 				while (rem.length && (rem[0] == '_' || std.uni.isAlpha(rem.front) || isDigit(rem.front)))
736 					rem.popFront();
737 				ret ~= dsource[0 .. $ - rem.length];
738 				dsource = rem;
739 				continue;
740 			}
741 
742 			// character literal?
743 			if( dsource[0] == '\'' ){
744 				size_t i = 1;
745 				while( dsource[i] != '\'' ){
746 					if( dsource[i] == '\\' ) i++;
747 					i++;
748 					enforce(i < dsource.length);
749 				}
750 				ret ~= dsource[0 .. i+1];
751 				dsource = dsource[i+1 .. $];
752 				continue;
753 			}
754 
755 			// string? (incomplete!)
756 			if( dsource[0] == '"' ){
757 				size_t i = 1;
758 				while( dsource[i] != '"' ){
759 					if( dsource[i] == '\\' ) i++;
760 					i++;
761 					enforce(i < dsource.length);
762 				}
763 				ret ~= dsource[0 .. i+1];
764 				dsource = dsource[i+1 .. $];
765 				continue;
766 			}
767 
768 			// number?
769 			if( isDigit(dsource[0]) || dsource[0] == '.' ){
770 				auto dscopy = dsource;
771 				parse!double(dscopy);
772 				ret ~= dsource[0 .. dsource.length-dscopy.length];
773 				dsource = dscopy;
774 				if (dsource.startsWith("u")) dsource.popFront();
775 				else if (dsource.startsWith("f")) dsource.popFront();
776 				continue;
777 			}
778 
779 			auto nb = dsource.stride();
780 			ret ~= dsource[0 .. nb];
781 			dsource = dsource[nb .. $];
782 		}
783 
784 		return ret;
785 	}
786 
787 	bool isDigit(dchar ch)
788 	{
789 		return ch >= '0' && ch <= '9';
790 	}
791 
792 	bool isIdent(string str)
793 	{
794 		static import std.uni;
795 
796 		if( str.length < 1 ) return false;
797 		foreach( i, dchar ch; str ){
798 			if( ch == '_' || std.uni.isAlpha(ch) ) continue;
799 			if( i > 0 && isDigit(ch) ) continue;
800 			return false;
801 		}
802 		return true;
803 	}
804 
805 	string fullStrip(string s)
806 	{
807 		string chars = " \t\r\n";
808 		while( s.length > 0 && chars.countUntil(s[0]) >= 0 ) s.popFront();
809 		while( s.length > 0 && chars.countUntil(s[$-1]) >= 0 ) s.popBack();
810 		return s;
811 	}
812 
813 	void insertIntoTypeMap(Declaration decl)
814 	{
815 		auto qname = decl.qualifiedName.to!string;
816 		m_typeMap[qname] = decl;
817 		auto idx = qname.indexOf('.');
818 		while (idx >= 0) {
819 			qname = qname[idx+1 .. $];
820 			m_typeMap[qname] = decl;
821 			idx = qname.indexOf('.');
822 		}
823 	}
824 
825 	private void resolveTypeDecl(ref Type tp, const(Entity) sc)
826 	{
827 		if (tp.kind != TypeKind.Primitive) return;
828 		if (tp.typeDecl) return;
829 
830 		tp.typeDecl = sc.lookup!Declaration(tp.typeName);
831 		if (!tp.typeDecl || !isTypeDecl(tp.typeDecl)) tp.typeDecl = m_typeMap.get(tp.typeName, null);
832 	}
833 
834 	private bool isTypeDecl(in Declaration a)
835 	{
836 		switch(a.kind){
837 			default: return false;
838 			case DeclarationKind.Struct:
839 			case DeclarationKind.Union:
840 			case DeclarationKind.Class:
841 			case DeclarationKind.Interface:
842 			case DeclarationKind.Enum:
843 				return true;
844 			case DeclarationKind.Alias:
845 				return !!(cast(AliasDeclaration)a).targetType;
846 			case DeclarationKind.TemplateParameter:
847 				return true;
848 			case DeclarationKind.Template:
849 				// support eponymous template types
850 				auto td = cast(TemplateDeclaration)a;
851 				// be optimistic for templates without content that they are in fact types
852 				if (!td.members.length) return true;
853 				// otherwise require an actual eponymous type member
854 				auto mi = td.members.countUntil!(m => m.name == a.name);
855 				return mi >= 0 && isTypeDecl(td.members[mi]);
856 		}
857 	}
858 }
859 
860 string demanglePrettyType(string mangled_type)
861 {
862 	if (mangled_type == "n") return "typeof(null)"; // Workaround D issue 14410
863 	auto str = assumeUnique(demangleType(mangled_type));
864 	str = str.replace("immutable(char)[]", "string");
865 	str = str.replace("immutable(wchar)[]", "wstring");
866 	str = str.replace("immutable(dchar)[]", "dstring");
867 	return str;
868 }