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", "scope", "shared"];
426 
427 		static immutable member_function_attribute_keywords = ["const", "immutable", "inout", "shared", "pure", "nothrow"];
428 
429 
430 		if( tokens.length > 0 && tokens[0] == "extern" ){
431 			enforce(tokens[1] == "(");
432 			enforce(tokens[3] == ")");
433 			attributes ~= CachedString(join(tokens[0 .. 4]));
434 			tokens = tokens[4 .. $];
435 		}
436 
437 		static immutable string[] attribute_keywords = global_attribute_keywords ~ parameter_attribute_keywords ~ member_function_attribute_keywords;
438 		/*final switch( sc ){
439 			case DeclScope.Global: attribute_keywords = global_attribute_keywords; break;
440 			case DeclScope.Parameter: attribute_keywords = parameter_attribute_keywords; break;
441 			case DeclScope.Class: attribute_keywords = member_function_attribute_keywords; break;
442 		}*/
443 
444 		void parseAttributes(const(string)[] keywords, scope void delegate(CachedString s) del)
445 		{
446 			while( tokens.length > 0 ){
447 				if( tokens.front == "@" ){
448 					tokens.popFront();
449 					del(CachedString("@"~tokens.front));
450 					tokens.popFront();
451 				} else if( keywords.countUntil(tokens[0]) >= 0 && tokens.length > 1 && tokens[1] != "(" ){
452 					del(CachedString(tokens.front));
453 					tokens.popFront();
454 				} else break;
455 			}
456 		}
457 
458 		parseAttributes(attribute_keywords, (k) { attributes ~= k; });
459 
460 
461 		Type type;
462 		static immutable const_modifiers = ["const", "immutable", "shared", "inout"];
463 		if (tokens.length > 2 && tokens[1] == "(" && const_modifiers.countUntil(tokens[0]) >= 0) {
464 			auto mod = tokens.front;
465 			tokens.popFrontN(2);
466 			CachedString[] subattrs;
467 			type = parseBasicType(tokens, sc, subattrs);
468 			type.modifiers ~= CachedString(mod);
469 			type.attributes ~= subattrs;
470 			enforce(!tokens.empty && tokens.front == ")", format("Missing ')' for '%s('", mod));
471 			tokens.popFront();
472 		} else if (!tokens.empty && !tokens.front.among("function", "delegate")) {
473 			type.kind = TypeKind.Primitive;
474 
475 			size_t start = 0, end;
476 			if( tokens[start] == "." ) start++;
477 			for( end = start; end < tokens.length && isIdent(tokens[end]); ){
478 				end++;
479 				if( end >= tokens.length || tokens[end] != "." )
480 					break;
481 				end++;
482 			}
483 
484 			size_t i = end;
485 
486 			string type_name, nested_name;
487 			if( i == 0 && tokens[0] == "..." ){
488 				type_name = "...";
489 				nested_name = null;
490 			} else 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 			if (!tokens.empty && tokens.front == ",") tokens.popFront(); // sometimes demangleType() returns something like "void(, ...)"
613 			while (true) {
614 				if (tokens.front == ")") break;
615 				enforce(!tokens.empty);
616 				ftype.parameterTypes ~= CachedType(parseTypeDecl(tokens, sc));
617 				string pname;
618 				if (tokens.front != "," && tokens.front != ")") {
619 					pname = tokens.front;
620 					tokens.popFront();
621 				}
622 				if (tokens.front == "...") {
623 					pname ~= tokens.front;
624 					tokens.popFront();
625 				}
626 				ftype._parameterNames ~= CachedString(pname);
627 				if (tokens.front == "=") {
628 					tokens.popFront();
629 					string defval;
630 					int ccount = 0;
631 					while (!tokens.empty) {
632 						if (ccount == 0 && (tokens.front == "," || tokens.front == ")"))
633 							break;
634 						if (tokens.front == "(") ccount++;
635 						else if (tokens.front == ")") ccount--;
636 						defval ~= tokens.front;
637 						tokens.popFront();
638 					}
639 					ftype._parameterDefaultValues ~= cast(immutable)parseValue(defval);
640 					logDebug("got defval %s", defval);
641 				} else ftype._parameterDefaultValues ~= null;
642 				if (tokens.front == ")") break;
643 				enforce(tokens.front == ",", "Expecting ',', got "~tokens.front);
644 				tokens.popFront();
645 			}
646 			tokens.popFront();
647 
648 			parseAttributes(member_function_attribute_keywords, (k) { ftype.attributes ~= cast(immutable)k; });
649 
650 			type = ftype;
651 		}
652 
653 		return type;
654 	}
655 
656 	/* special function that looks at the default type to see if a function type
657 		is expected and if that's the case, fixes up the type string to read
658 		as a valid D type declaration (DMD omits "function"/"delegate", which
659 		results in an ambiguous meaning)
660 	*/
661 	private string fixFunctionType(string type, string deftype)
662 	{
663 		Type dt = parseType(deftype, new Module(null, "dummy"));
664 		if (deftype == "void()" || dt != Type.init && dt.kind.among(TypeKind.Function, TypeKind.Delegate)) {
665 			auto last_clamp = type.lastIndexOf(')');
666 			auto idx = last_clamp-1;
667 			int l = 1;
668 			while (idx >= 0) {
669 				if (type[idx] == ')') l++;
670 				else if (type[idx] == '(') l--;
671 				if (l == 0) break;
672 				idx--;
673 			}
674 			if (idx <= 0 || l > 0) return type;
675 			return type[0 .. idx] ~ " function" ~ type[idx .. $];
676 		}
677 		return type;
678 	}
679 
680 	string[] tokenizeDSource(string dsource)
681 	{
682 		static import std.uni;
683 		import std.utf : stride;
684 
685 		static immutable string[] tokens = [
686 			"/", "/=", ".", "..", "...", "&", "&=", "&&", "|", "|=", "||",
687 			"-", "-=", "--", "+", "+=", "++", "<", "<=", "<<", "<<=",
688 			"<>", "<>=", ">", ">=", ">>=", ">>>=", ">>", ">>>", "!", "!=",
689 			"!<>", "!<>=", "!<", "!<=", "!>", "!>=", "(", ")", "[", "]",
690 			"{", "}", "?", ",", ";", ":", "$", "=", "==", "*", "*=",
691 			"%", "%=", "^", "^=", "~", "~=", "@", "=>", "#", "C++"
692 		];
693 		static bool[string] token_map;
694 
695 		if (token_map is null) {
696 			foreach (t; tokens)
697 				token_map[t] = true;
698 			token_map.rehash;
699 		}
700 
701 		string[] ret;
702 		outer:
703 		while(true){
704 			dsource = stripLeft(dsource);
705 			if( dsource.length == 0 ) break;
706 
707 			// special token?
708 			foreach_reverse (i; 1 .. min(5, dsource.length+1))
709 				if (dsource[0 .. i] in token_map) {
710 					ret ~= dsource[0 .. i];
711 					dsource = dsource[i .. $];
712 					continue outer;
713 				}
714 
715 			// identifier?
716 			if( dsource[0] == '_' || std.uni.isAlpha(dsource.front) ){
717 				size_t i = 1;
718 				string rem = dsource;
719 				rem.popFront();
720 				while (rem.length && (rem[0] == '_' || std.uni.isAlpha(rem.front) || isDigit(rem.front)))
721 					rem.popFront();
722 				ret ~= dsource[0 .. $ - rem.length];
723 				dsource = rem;
724 				continue;
725 			}
726 
727 			// character literal?
728 			if( dsource[0] == '\'' ){
729 				size_t i = 1;
730 				while( dsource[i] != '\'' ){
731 					if( dsource[i] == '\\' ) i++;
732 					i++;
733 					enforce(i < dsource.length);
734 				}
735 				ret ~= dsource[0 .. i+1];
736 				dsource = dsource[i+1 .. $];
737 				continue;
738 			}
739 
740 			// string? (incomplete!)
741 			if( dsource[0] == '"' ){
742 				size_t i = 1;
743 				while( dsource[i] != '"' ){
744 					if( dsource[i] == '\\' ) i++;
745 					i++;
746 					enforce(i < dsource.length);
747 				}
748 				ret ~= dsource[0 .. i+1];
749 				dsource = dsource[i+1 .. $];
750 				continue;
751 			}
752 
753 			// number?
754 			if( isDigit(dsource[0]) || dsource[0] == '.' ){
755 				auto dscopy = dsource;
756 				parse!double(dscopy);
757 				ret ~= dsource[0 .. dsource.length-dscopy.length];
758 				dsource = dscopy;
759 				if (dsource.startsWith("u")) dsource.popFront();
760 				else if (dsource.startsWith("f")) dsource.popFront();
761 				continue;
762 			}
763 
764 			auto nb = dsource.stride();
765 			ret ~= dsource[0 .. nb];
766 			dsource = dsource[nb .. $];
767 		}
768 
769 		return ret;
770 	}
771 
772 	bool isDigit(dchar ch)
773 	{
774 		return ch >= '0' && ch <= '9';
775 	}
776 
777 	bool isIdent(string str)
778 	{
779 		static import std.uni;
780 
781 		if( str.length < 1 ) return false;
782 		foreach( i, dchar ch; str ){
783 			if( ch == '_' || std.uni.isAlpha(ch) ) continue;
784 			if( i > 0 && isDigit(ch) ) continue;
785 			return false;
786 		}
787 		return true;
788 	}
789 
790 	string fullStrip(string s)
791 	{
792 		string chars = " \t\r\n";
793 		while( s.length > 0 && chars.countUntil(s[0]) >= 0 ) s.popFront();
794 		while( s.length > 0 && chars.countUntil(s[$-1]) >= 0 ) s.popBack();
795 		return s;
796 	}
797 
798 	void insertIntoTypeMap(Declaration decl)
799 	{
800 		auto qname = decl.qualifiedName.to!string;
801 		m_typeMap[qname] = decl;
802 		auto idx = qname.indexOf('.');
803 		while (idx >= 0) {
804 			qname = qname[idx+1 .. $];
805 			m_typeMap[qname] = decl;
806 			idx = qname.indexOf('.');
807 		}
808 	}
809 
810 	private void resolveTypeDecl(ref Type tp, const(Entity) sc)
811 	{
812 		if (tp.kind != TypeKind.Primitive) return;
813 		if (tp.typeDecl) return;
814 
815 		tp.typeDecl = sc.lookup!Declaration(tp.typeName);
816 		if (!tp.typeDecl || !isTypeDecl(tp.typeDecl)) tp.typeDecl = m_typeMap.get(tp.typeName, null);
817 	}
818 
819 	private bool isTypeDecl(in Declaration a)
820 	{
821 		switch(a.kind){
822 			default: return false;
823 			case DeclarationKind.Struct:
824 			case DeclarationKind.Union:
825 			case DeclarationKind.Class:
826 			case DeclarationKind.Interface:
827 			case DeclarationKind.Enum:
828 				return true;
829 			case DeclarationKind.Alias:
830 				return !!(cast(AliasDeclaration)a).targetType;
831 			case DeclarationKind.TemplateParameter:
832 				return true;
833 			case DeclarationKind.Template:
834 				// support eponymous template types
835 				auto td = cast(TemplateDeclaration)a;
836 				// be optimistic for templates without content that they are in fact types
837 				if (!td.members.length) return true;
838 				// otherwise require an actual eponymous type member
839 				auto mi = td.members.countUntil!(m => m.name == a.name);
840 				return mi >= 0 && isTypeDecl(td.members[mi]);
841 		}
842 	}
843 }
844 
845 string demanglePrettyType(string mangled_type)
846 {
847 	if (mangled_type == "n") return "typeof(null)"; // Workaround D issue 14410
848 	auto str = assumeUnique(demangleType(mangled_type));
849 	str = str.replace("immutable(char)[]", "string");
850 	str = str.replace("immutable(wchar)[]", "wstring");
851 	str = str.replace("immutable(dchar)[]", "dstring");
852 	return str;
853 }