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 			string specvalue = arg["specValue"].opt!string;
319 			bool needs_type_parse = false;
320 
321 			switch (arg["kind"].get!string) {
322 				default: break;
323 				case "value":
324 					needs_type_parse = true;
325 					break;
326 				case "alias":
327 					pname = "alias " ~ pname;
328 					break;
329 				case "tuple":
330 					pname ~= "...";
331 					break;
332 			}
333 
334 			auto pdecl = new TemplateParameterDeclaration(ret, pname);
335 			pdecl.defaultValue = defvalue;
336 			pdecl.specValue = specvalue;
337 			ret.templateArgs ~= pdecl;
338 			ret.templateConstraint = json["constraint"].opt!string;
339 
340 			if (needs_type_parse)
341 				m_declarations ~= tuple(cast(Declaration)pdecl, arg);
342 		}
343 		ret.members = parseDeclList(json["members"], ret);
344 		return ret;
345 	}
346 
347 	Type parseType(Json json, Entity sc, string def_type = "void", bool warn_if_not_exists = true)
348 		out(ret) { assert(!def_type.length || ret != Type.init); }
349 	body {
350 		string str;
351 		if( json.type == Json.Type.Undefined ){
352 			if (warn_if_not_exists) logWarn("No type found for %s.", sc.qualifiedName);
353 			str = def_type;
354 		} else if (json.type == Json.Type.String) str = json.get!string();
355 		else if (auto pv = "deco" in json) str = demanglePrettyType(pv.get!string());
356 		else if (auto pv = "type" in json) str = fixFunctionType(pv.get!string(), def_type);
357 		else if (auto pv = "originalType" in json) str = fixFunctionType(pv.get!string(), def_type);
358 
359 		if( str.length == 0 ) str = def_type;
360 
361 		if( !str.length ) return Type.init;
362 
363 		return parseType(str, sc);
364 	}
365 
366 	Type parseType(string str, Entity sc)
367 		out(ret) { assert(ret != Type.init); }
368 	body {
369 		auto tokens = tokenizeDSource(str);
370 
371 		logDebug("parse type '%s'", str);
372 		try {
373 			auto type = parseTypeDecl(tokens, sc);
374 			type.text = str;
375 			return type;
376 		} catch( Exception e ){
377 			logError("Error parsing type '%s': %s", str, e.msg);
378 			Type type;
379 			type.text = str;
380 			type.typeName = str;
381 			type.kind = TypeKind.Primitive;
382 			return type;
383 		}
384 	}
385 
386 	Value parseValue(string str)
387 	{
388 		auto ret = new Value;
389 		//ret.type = ;
390 		ret.valueString = str;
391 		return ret;
392 	}
393 
394 	Protection parseProtection(Json prot)
395 	{
396 		switch( prot.opt!string ){
397 			default: return Protection.Public;
398 			case "package": return Protection.Package;
399 			case "protected": return Protection.Protected;
400 			case "private": return Protection.Private;
401 		}
402 	}
403 
404 	Type parseTypeDecl(ref string[] tokens, Entity sc)
405 	{
406 
407 		auto ret = parseType(tokens, sc);
408 		return ret;
409 	}
410 
411 	Type parseType(ref string[] tokens, Entity sc)
412 	{
413 		CachedString[] attributes;
414 		auto basic_type = parseBasicType(tokens, sc, attributes);
415 		basic_type.attributes ~= attributes;
416 		return basic_type;
417 	}
418 
419 	Type parseBasicType(ref string[] tokens, Entity sc, out CachedString[] attributes)
420 		out(ret) { assert(ret != Type.init); }
421 	body {
422 		static immutable global_attribute_keywords = ["abstract", "auto", "const", "deprecated", "enum",
423 			"extern", "final", "immutable", "inout", "shared", "nothrow", "override", "pure",
424 			"__gshared", "scope", "static", "synchronize"];
425 
426 		static immutable parameter_attribute_keywords = ["auto", "const", "final", "immutable", "in", "inout",
427 			"lazy", "out", "ref", "return", "scope", "shared"];
428 
429 		static immutable member_function_attribute_keywords = ["const", "immutable", "inout", "ref", "return",
430 			"scope", "shared", "pure", "nothrow"];
431 
432 
433 		if( tokens.length > 0 && tokens[0] == "extern" ){
434 			enforce(tokens[1] == "(");
435 			enforce(tokens[3] == ")");
436 			attributes ~= CachedString(join(tokens[0 .. 4]));
437 			tokens = tokens[4 .. $];
438 		}
439 
440 		static immutable string[] attribute_keywords = global_attribute_keywords ~ parameter_attribute_keywords ~ member_function_attribute_keywords;
441 		/*final switch( sc ){
442 			case DeclScope.Global: attribute_keywords = global_attribute_keywords; break;
443 			case DeclScope.Parameter: attribute_keywords = parameter_attribute_keywords; break;
444 			case DeclScope.Class: attribute_keywords = member_function_attribute_keywords; break;
445 		}*/
446 
447 		void parseAttributes(const(string)[] keywords, scope void delegate(CachedString s) del)
448 		{
449 			while( tokens.length > 0 ){
450 				if( tokens.front == "@" ){
451 					tokens.popFront();
452 					del(CachedString("@"~tokens.front));
453 					tokens.popFront();
454 				} else if( keywords.countUntil(tokens[0]) >= 0 && tokens.length > 1 && tokens[1] != "(" ){
455 					del(CachedString(tokens.front));
456 					tokens.popFront();
457 				} else break;
458 			}
459 		}
460 
461 		parseAttributes(attribute_keywords, (k) { attributes ~= k; });
462 
463 
464 		Type type;
465 		static immutable const_modifiers = ["const", "immutable", "shared", "inout"];
466 		if (tokens.length > 2 && tokens[1] == "(" && const_modifiers.countUntil(tokens[0]) >= 0) {
467 			auto mod = tokens.front;
468 			tokens.popFrontN(2);
469 			CachedString[] subattrs;
470 			type = parseBasicType(tokens, sc, subattrs);
471 			type.modifiers ~= CachedString(mod);
472 			type.attributes ~= subattrs;
473 			enforce(!tokens.empty && tokens.front == ")", format("Missing ')' for '%s('", mod));
474 			tokens.popFront();
475 		} else if (!tokens.empty && !tokens.front.among("function", "delegate")) {
476 			type.kind = TypeKind.Primitive;
477 
478 			size_t start = 0, end;
479 			if( tokens[start] == "." ) start++;
480 			for( end = start; end < tokens.length && isIdent(tokens[end]); ){
481 				end++;
482 				if( end >= tokens.length || tokens[end] != "." )
483 					break;
484 				end++;
485 			}
486 
487 			size_t i = end;
488 
489 			string type_name, nested_name;
490 			if( i == 0 && tokens[0] == "(" ){
491 				type_name = "constructor";
492 				nested_name = null;
493 			} else {
494 				enforce(i > 0, "Expected identifier but got "~tokens.front);
495 				auto unqualified_name = tokens[end - 1];
496 				type.typeName = join(tokens[start .. end]);
497 				//
498 				tokens.popFrontN(i);
499 
500 				if (type.typeName == "typeof" && !tokens.empty && tokens.front == "(") {
501 					type.typeName ~= "(";
502 					tokens.popFront();
503 					int level = 1;
504 					while (!tokens.empty && level > 0) {
505 						if (tokens.front == "(") level++;
506 						else if( tokens.front == ")") level--;
507 						type.typeName ~= tokens.front;
508 						tokens.popFront();
509 					}
510 				} else if( !tokens.empty && tokens.front == "!" ){
511 					tokens.popFront();
512 					if( tokens.front == "(" ){
513 						size_t j = 1;
514 						int cc = 1;
515 						while( cc > 0 ){
516 							assert(j < tokens.length);
517 							if( tokens[j] == "(" ) cc++;
518 							else if( tokens[j] == ")") cc--;
519 							j++;
520 						}
521 						type.templateArgs = join(tokens[0 .. j]);
522 						tokens.popFrontN(j);
523 						logDebug("templargs: %s", type.templateArgs);
524 					} else {
525 						type.templateArgs = tokens[0];
526 						tokens.popFront();
527 					}
528 
529 					// resolve eponymous template member, e.g. test.Foo!int.Foo
530 					if (!tokens.empty && tokens.front == ".") {
531 						tokens.popFront();
532 						if (!tokens.empty && tokens.front == unqualified_name) { // eponymous template
533 							resolveTypeDecl(type, sc);
534 							auto tdecl = cast(TemplateDeclaration)type.typeDecl;
535 							auto members = tdecl ? tdecl.members : null;
536 							auto mi = members.countUntil!(m => m.name == tokens.front);
537 							assert(mi >= 0 || members.empty);
538 							if (mi >= 0)
539 								type.typeDecl = members[mi];
540 							tokens.popFront();
541 						}
542 					}
543 					// HACK: dropping the actual type name here!
544 					// TODO: resolve other members and adjust typeName,
545 					// e.g. test.Foo!int.Enum, test.Foo!int.Bar!int, test.Foo!int.Struct.Templ!(int, double)
546 					while (!tokens.empty && tokens.front == ".") {
547  						tokens.popFront();
548 						if (!tokens.empty()) tokens.popFront();
549 					}
550 				}
551 
552 				resolveTypeDecl(type, sc);
553 			}
554 		}
555 
556 		while( !tokens.empty ){
557 			if( tokens.front == "*" ){
558 				Type ptr;
559 				ptr.kind = TypeKind.Pointer;
560 				ptr.elementType = type;
561 				type = ptr;
562 				tokens.popFront();
563 			} else if( tokens.front == "[" ){
564 				tokens.popFront();
565 				enforce(!tokens.empty, "Missing ']'.");
566 				if( tokens.front == "]" ){
567 					Type arr;
568 					arr.kind = TypeKind.Array;
569 					arr.elementType = type;
570 					type = arr;
571 				} else {
572 					string[] tokens_copy = tokens;
573 					Type keytp;
574 					if (!isDigit(tokens.front[0]) && tokens.front != "!") keytp = parseType(tokens_copy, sc);
575 					if (keytp != Type.init && !tokens_copy.empty && tokens_copy.front == "]") {
576 						tokens = tokens_copy;
577 						Type aa;
578 						aa.kind = TypeKind.AssociativeArray;
579 						aa.elementType = type;
580 						aa.keyType = keytp;
581 						type = aa;
582 					} else {
583 						Type arr;
584 						arr.kind = TypeKind.StaticArray;
585 						arr.elementType = type;
586 						arr.arrayLength = tokens.front;
587 						tokens.popFront();
588 						while (!tokens.empty && tokens.front != "]") {
589 							arr.arrayLength ~= tokens.front;
590 							tokens.popFront();
591 						}
592 						type = arr;
593 					}
594 				}
595 				enforce(tokens.front == "]", "Expected ']', got '"~tokens.front~"'.");
596 				tokens.popFront();
597 			} else break;
598 		}
599 
600 		if (type == Type.init) {
601 			type.kind = TypeKind.Primitive;
602 			type.typeName = "auto";
603 		}
604 
605 		while (!tokens.empty && (tokens.front == "function" || tokens.front == "delegate" || tokens.front == "(")) {
606 			Type ftype;
607 			ftype.kind = tokens.front == "(" || tokens.front == "function" ? TypeKind.Function : TypeKind.Delegate;
608 			ftype.returnType = type;
609 			if (tokens.front != "(") tokens.popFront();
610 			enforce(tokens.front == "(");
611 			tokens.popFront();
612 			// demangleType() returns something like "void(, ...)" for variadic functions or "void(, type)" for typeof(null) parameters
613 			if (!tokens.empty && tokens.front == ",") tokens.popFront();
614 			// (...) - D variadic function
615 			if (tokens.front == "...") {
616 				ftype.variadic = Type.Variadic.d;
617 				tokens.popFront();
618 			}
619 			while (true) {
620 				if (tokens.front == ")") break;
621 
622 				// (int) - parameter type
623 				enforce(!tokens.empty);
624 				ftype.parameterTypes ~= CachedType(parseTypeDecl(tokens, sc));
625 
626 
627 				// (int[]...), (Clazz...) - typesafe variadic function
628 				if (tokens.front == "...") {
629 					ftype.variadic = Type.Variadic.typesafe;
630 					tokens.popFront();
631 				}
632 				// (type, ...) - D or extern(C) variadic
633 				else if (tokens.length > 2 && tokens[0] == "," && tokens[1] == "...") {
634 					ftype.variadic = Type.Variadic.c; // c and d treated identical for doc-gen
635 					tokens.popFrontN(2);
636 				}
637 
638 				string pname;
639 				if (tokens.front != "," && tokens.front != ")") {
640 					pname = tokens.front;
641 					tokens.popFront();
642 				}
643 				ftype._parameterNames ~= CachedString(pname);
644 				if (tokens.front == "=") {
645 					tokens.popFront();
646 					string defval;
647 					int ccount = 0;
648 					while (!tokens.empty) {
649 						if (ccount == 0 && (tokens.front == "," || tokens.front == ")"))
650 							break;
651 						if (tokens.front == "(") ccount++;
652 						else if (tokens.front == ")") ccount--;
653 						defval ~= tokens.front;
654 						tokens.popFront();
655 					}
656 					ftype._parameterDefaultValues ~= cast(immutable)parseValue(defval);
657 					logDebug("got defval %s", defval);
658 				} else ftype._parameterDefaultValues ~= null;
659 				if (tokens.front == ")") break;
660 				enforce(tokens.front == ",", "Expecting ',', got "~tokens.front);
661 				tokens.popFront();
662 			}
663 			tokens.popFront();
664 
665 			parseAttributes(member_function_attribute_keywords, (k) { ftype.attributes ~= cast(immutable)k; });
666 
667 			type = ftype;
668 		}
669 
670 		return type;
671 	}
672 
673 	/* special function that looks at the default type to see if a function type
674 		is expected and if that's the case, fixes up the type string to read
675 		as a valid D type declaration (DMD omits "function"/"delegate", which
676 		results in an ambiguous meaning)
677 	*/
678 	private string fixFunctionType(string type, string deftype)
679 	{
680 		Type dt = parseType(deftype, new Module(null, "dummy"));
681 		if (deftype == "void()" || dt != Type.init && dt.kind.among(TypeKind.Function, TypeKind.Delegate)) {
682 			auto last_clamp = type.lastIndexOf(')');
683 			auto idx = last_clamp-1;
684 			int l = 1;
685 			while (idx >= 0) {
686 				if (type[idx] == ')') l++;
687 				else if (type[idx] == '(') l--;
688 				if (l == 0) break;
689 				idx--;
690 			}
691 			if (idx <= 0 || l > 0) return type;
692 			return type[0 .. idx] ~ " function" ~ type[idx .. $];
693 		}
694 		return type;
695 	}
696 
697 	string[] tokenizeDSource(string dsource)
698 	{
699 		static import std.uni;
700 		import std.utf : stride;
701 
702 		static immutable string[] tokens = [
703 			"/", "/=", ".", "..", "...", "&", "&=", "&&", "|", "|=", "||",
704 			"-", "-=", "--", "+", "+=", "++", "<", "<=", "<<", "<<=",
705 			"<>", "<>=", ">", ">=", ">>=", ">>>=", ">>", ">>>", "!", "!=",
706 			"!<>", "!<>=", "!<", "!<=", "!>", "!>=", "(", ")", "[", "]",
707 			"{", "}", "?", ",", ";", ":", "$", "=", "==", "*", "*=",
708 			"%", "%=", "^", "^=", "~", "~=", "@", "=>", "#", "C++"
709 		];
710 		static bool[string] token_map;
711 
712 		if (token_map is null) {
713 			foreach (t; tokens)
714 				token_map[t] = true;
715 			token_map.rehash;
716 		}
717 
718 		string[] ret;
719 		outer:
720 		while(true){
721 			dsource = stripLeft(dsource);
722 			if( dsource.length == 0 ) break;
723 
724 			// special token?
725 			foreach_reverse (i; 1 .. min(5, dsource.length+1))
726 				if (dsource[0 .. i] in token_map) {
727 					ret ~= dsource[0 .. i];
728 					dsource = dsource[i .. $];
729 					continue outer;
730 				}
731 
732 			// identifier?
733 			if( dsource[0] == '_' || std.uni.isAlpha(dsource.front) ){
734 				size_t i = 1;
735 				string rem = dsource;
736 				rem.popFront();
737 				while (rem.length && (rem[0] == '_' || std.uni.isAlpha(rem.front) || isDigit(rem.front)))
738 					rem.popFront();
739 				ret ~= dsource[0 .. $ - rem.length];
740 				dsource = rem;
741 				continue;
742 			}
743 
744 			// character literal?
745 			if( dsource[0] == '\'' ){
746 				size_t i = 1;
747 				while( dsource[i] != '\'' ){
748 					if( dsource[i] == '\\' ) i++;
749 					i++;
750 					enforce(i < dsource.length);
751 				}
752 				ret ~= dsource[0 .. i+1];
753 				dsource = dsource[i+1 .. $];
754 				continue;
755 			}
756 
757 			// string? (incomplete!)
758 			if( dsource[0] == '"' ){
759 				size_t i = 1;
760 				while( dsource[i] != '"' ){
761 					if( dsource[i] == '\\' ) i++;
762 					i++;
763 					enforce(i < dsource.length);
764 				}
765 				ret ~= dsource[0 .. i+1];
766 				dsource = dsource[i+1 .. $];
767 				continue;
768 			}
769 
770 			// number?
771 			if( isDigit(dsource[0]) || dsource[0] == '.' ){
772 				auto dscopy = dsource;
773 				parse!double(dscopy);
774 				ret ~= dsource[0 .. dsource.length-dscopy.length];
775 				dsource = dscopy;
776 				if (dsource.startsWith("u")) dsource.popFront();
777 				else if (dsource.startsWith("f")) dsource.popFront();
778 				continue;
779 			}
780 
781 			auto nb = dsource.stride();
782 			ret ~= dsource[0 .. nb];
783 			dsource = dsource[nb .. $];
784 		}
785 
786 		return ret;
787 	}
788 
789 	bool isDigit(dchar ch)
790 	{
791 		return ch >= '0' && ch <= '9';
792 	}
793 
794 	bool isIdent(string str)
795 	{
796 		static import std.uni;
797 
798 		if( str.length < 1 ) return false;
799 		foreach( i, dchar ch; str ){
800 			if( ch == '_' || std.uni.isAlpha(ch) ) continue;
801 			if( i > 0 && isDigit(ch) ) continue;
802 			return false;
803 		}
804 		return true;
805 	}
806 
807 	string fullStrip(string s)
808 	{
809 		string chars = " \t\r\n";
810 		while( s.length > 0 && chars.countUntil(s[0]) >= 0 ) s.popFront();
811 		while( s.length > 0 && chars.countUntil(s[$-1]) >= 0 ) s.popBack();
812 		return s;
813 	}
814 
815 	void insertIntoTypeMap(Declaration decl)
816 	{
817 		auto qname = decl.qualifiedName.to!string;
818 		m_typeMap[qname] = decl;
819 		auto idx = qname.indexOf('.');
820 		while (idx >= 0) {
821 			qname = qname[idx+1 .. $];
822 			m_typeMap[qname] = decl;
823 			idx = qname.indexOf('.');
824 		}
825 	}
826 
827 	private void resolveTypeDecl(ref Type tp, const(Entity) sc)
828 	{
829 		if (tp.kind != TypeKind.Primitive) return;
830 		if (tp.typeDecl) return;
831 
832 		tp.typeDecl = sc.lookup!Declaration(tp.typeName);
833 		if (!tp.typeDecl || !isTypeDecl(tp.typeDecl)) tp.typeDecl = m_typeMap.get(tp.typeName, null);
834 	}
835 
836 	private bool isTypeDecl(in Declaration a)
837 	{
838 		switch(a.kind){
839 			default: return false;
840 			case DeclarationKind.Struct:
841 			case DeclarationKind.Union:
842 			case DeclarationKind.Class:
843 			case DeclarationKind.Interface:
844 			case DeclarationKind.Enum:
845 				return true;
846 			case DeclarationKind.Alias:
847 				return !!(cast(AliasDeclaration)a).targetType;
848 			case DeclarationKind.TemplateParameter:
849 				return true;
850 			case DeclarationKind.Template:
851 				// support eponymous template types
852 				auto td = cast(TemplateDeclaration)a;
853 				// be optimistic for templates without content that they are in fact types
854 				if (!td.members.length) return true;
855 				// otherwise require an actual eponymous type member
856 				auto mi = td.members.countUntil!(m => m.name == a.name);
857 				return mi >= 0 && isTypeDecl(td.members[mi]);
858 		}
859 	}
860 }
861 
862 string demanglePrettyType(string mangled_type)
863 {
864 	if (mangled_type == "n") return "typeof(null)"; // Workaround D issue 14410
865 	auto str = assumeUnique(demangleType(mangled_type));
866 	str = str.replace("immutable(char)[]", "string");
867 	str = str.replace("immutable(wchar)[]", "wstring");
868 	str = str.replace("immutable(dchar)[]", "dstring");
869 	return str;
870 }