1 /**
2 	Parses DMD JSON output and builds up a documentation syntax tree (JSON format from DMD 2.063.2).
3 
4 	Copyright: © 2012 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.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.parseModule(mod, root);
31 	}
32 	p.resolveTypes(root);
33 	return root;
34 }
35 
36 private struct Parser
37 {
38 	private Tuple!(Type, Entity)[] m_primTypes;
39 	private Declaration[string] m_typeMap;
40 
41 	void resolveTypes(Package root)
42 	{
43 		bool isTypeDecl(Declaration a)
44 		{
45 			switch(a.kind){
46 				default: return false;
47 				case DeclarationKind.Struct:
48 				case DeclarationKind.Union:
49 				case DeclarationKind.Class:
50 				case DeclarationKind.Interface:
51 				case DeclarationKind.Enum:
52 					return true;
53 				case DeclarationKind.Alias:
54 					return (cast(AliasDeclaration)a).targetType !is null;
55 				case DeclarationKind.TemplateParameter:
56 					return true;
57 			}
58 		}
59 
60 		foreach (t; m_primTypes) {
61 			auto decl = t[1].lookup!Declaration(t[0].typeName);
62 			if (!decl || !isTypeDecl(decl)) {
63 				auto pd = t[0].typeName in m_typeMap;
64 				if (pd) decl = *pd;
65 			}
66 			if (decl && isTypeDecl(decl))
67 				t[0].typeDecl = decl;
68 		}
69 
70 		// fixup class bases
71 		root.visit!ClassDeclaration((decl){
72 			if( decl.baseClass && decl.baseClass.typeDecl && !cast(ClassDeclaration)decl.baseClass.typeDecl )
73 				decl.baseClass = null;
74 			foreach( i; decl.derivedInterfaces )
75 				if( i.typeDecl && !cast(InterfaceDeclaration)i.typeDecl )
76 					i.typeDecl = null;
77 			assert(decl);
78 		});
79 
80 		// fixup interface bases
81 		root.visit!InterfaceDeclaration((decl){
82 			foreach( i; decl.derivedInterfaces )
83 				if( i.typeDecl && !cast(InterfaceDeclaration)i.typeDecl )
84 					i.typeDecl = null;
85 			assert(decl);
86 		});
87 	}
88 
89 	void parseModule(Json json, Package root_package)
90 	{
91 		Module mod;
92 		if( "name" !in json ){
93 			logError("No name attribute in module %s - ignoring", json.filename.opt!string);
94 			return;
95 		}
96 		auto path = json.name.get!string.split(".");
97 		Package p = root_package;
98 		foreach( i, pe; path ){
99 			if( i+1 < path.length ) p = p.getOrAddPackage(pe);
100 			else mod = p.createModule(pe);
101 		}
102 
103 		mod.file = json.file.get!string;
104 		mod.docGroup = new DocGroup(mod, json.comment.opt!string());
105 		mod.members = parseDeclList(json.members, mod);
106 	}
107 
108 	Declaration[] parseDeclList(Json json, Entity parent)
109 	{
110 		if( json.type == Json.Type.Undefined ) return null;
111 		DocGroup lastdoc;
112 		Declaration[] ret;
113 		foreach( mem; json ){
114 			auto decl = parseDecl(mem, parent);
115 			if( !decl ) continue;
116 			auto doc = decl.docGroup;
117 			if( lastdoc && (doc.text == lastdoc.text && doc.text.length || doc.comment.isDitto) ){
118 				lastdoc.members ~= decl;
119 				decl.docGroup = lastdoc;
120 			} else if( doc.comment.isPrivate ){
121 				decl.protection = Protection.Private;
122 				lastdoc = null;
123 			} else lastdoc = decl.docGroup;
124 			ret ~= decl;
125 		}
126 		return ret;
127 	}
128 
129 	Declaration parseDecl(Json json, Entity parent)
130 	{
131 		Declaration ret;
132 
133 		// DMD outputs templates with the wrong kind sometimes
134 		if( json.name.get!string().canFind('(') ){
135 			ret = parseTemplateDecl(json, parent);
136 		} else {
137 			switch( json.kind.get!string ){
138 				default:
139 					logWarn("Unknown declaration kind: %s", json.kind.get!string);
140 					return null;
141 				case "import":
142 				case "static import":
143 					// TODO: use for symbol resolving
144 					return null;
145 				case "destructor": return null;
146 				case "mixin": return null; // TODO: support documented mixins
147 				case "alias":
148 					ret = parseAliasDecl(json, parent);
149 					break;
150 				case "function":
151 				case "allocator":
152 				case "deallocator":
153 				case "constructor":
154 					ret = parseFunctionDecl(json, parent);
155 					break;
156 				case "enum":
157 					ret = parseEnumDecl(json, parent);
158 					break;
159 				case "enum member":
160 					ret = parseEnumMemberDecl(json, parent);
161 					break;
162 				case "struct":
163 				case "union":
164 				case "class":
165 				case "interface":
166 					ret = parseCompositeDecl(json, parent);
167 					break;
168 				case "variable":
169 					ret = parseVariableDecl(json, parent);
170 					break;
171 				case "template":
172 					ret = parseTemplateDecl(json, parent);
173 					break;
174 			}
175 		}
176 
177 		ret.protection = parseProtection(json.protection);
178 		ret.line = json["line"].opt!int;
179 		ret.docGroup = new DocGroup(ret, json.comment.opt!string());
180 
181 		return ret;
182 	}
183 
184 	auto parseAliasDecl(Json json, Entity parent)
185 	{
186 		auto ret = new AliasDeclaration(parent, json.name.get!string);
187 		ret.attributes = json.storageClass.opt!(Json[]).map!(j => j.get!string).array;
188 		ret.targetType = parseType(json, ret, null);
189 		if( ret.targetType && ret.targetType.kind == TypeKind.Primitive && ret.targetType.typeName.length == 0 )
190 			ret.targetType = null;
191 		insertIntoTypeMap(ret);
192 		return ret;
193 	}
194 
195 	auto parseFunctionDecl(Json json, Entity parent)
196 	{
197 		auto ret = new FunctionDeclaration(parent, json.name.opt!string);
198 		ret.type = parseType(json, ret, "void()");
199 		// TODO: use "storageClass" and "parameters" fields
200 		if( ret.type.kind == TypeKind.Function ){
201 			ret.returnType = ret.type.returnType;
202 			ret.attributes = ret.type.attributes ~ ret.type.modifiers;
203 			if (auto psc = "storageClass" in json)
204 				foreach (sc; *psc)
205 					if (!ret.attributes.canFind(sc.get!string))
206 						ret.attributes ~= sc.get!string;
207 			auto params = json.parameters.opt!(Json[]);
208 			foreach (i, pt; ret.type.parameterTypes) {
209 				auto pname = ret.type._parameterNames[i];
210 				auto pdefval = ret.type._parameterDefaultValues[i];
211 				if (i < params.length && params[i].name.type == Json.Type.String)
212 					pname = params[i].name.get!string();
213 				auto decl = new VariableDeclaration(ret, pname);
214 				decl.type = pt;
215 				decl.initializer = pdefval;
216 				ret.parameters ~= decl;
217 			}
218 			foreach (size_t i, pn; json.opt!(Json[]))
219 				ret.parameters[i].name = pn.get!string();
220 		} else {
221 			logError("Expected function type for '%s'/'%s', got %s %s", json["type"].opt!string, demangleType(json["deco"].opt!string), ret.type.kind, ret.type.typeName);
222 		}
223 		return ret;
224 	}
225 
226 	auto parseEnumDecl(Json json, Entity parent)
227 	{
228 		auto ret = new EnumDeclaration(parent, json.name.get!string);
229 		insertIntoTypeMap(ret);
230 		if( "base" !in json ){ // FIXME: parse deco instead
231 			if( auto pd = "baseDeco" in json )
232 				json.base = demanglePrettyType(pd.get!string());
233 		}
234 		ret.baseType = parseType(json.base, ret);
235 		auto mems = parseDeclList(json.members, ret);
236 		foreach( m; mems ){
237 			auto em = cast(EnumMemberDeclaration)m;
238 			assert(em !is null, "Enum containing non-enum-members?");
239 			ret.members ~= em;
240 		}
241 		return ret;
242 	}
243 
244 	auto parseEnumMemberDecl(Json json, Entity parent)
245 	{
246 		auto ret = new EnumMemberDeclaration(parent, json.name.get!string);
247 		//ret.value = parseValue(json.value);
248 		return ret;
249 	}
250 
251 	auto parseCompositeDecl(Json json, Entity parent)
252 	{
253 		CompositeTypeDeclaration ret;
254 		switch(json.kind.get!string){
255 			default:
256 				logWarn("Invalid composite decl kind: %s", json.kind.get!string);
257 				return new StructDeclaration(parent, json.name.get!string);
258 			case "struct":
259 				ret = new StructDeclaration(parent, json.name.get!string);
260 				break;
261 			case "union":
262 				ret = new UnionDeclaration(parent, json.name.get!string);
263 				break;
264 			case "class":
265 				auto clsdecl = new ClassDeclaration(parent, json.name.get!string);
266 				if( clsdecl.qualifiedName != "object.Object" )
267 					clsdecl.baseClass = parseType(json.base, clsdecl, "Object", false);
268 				foreach( intf; json.interfaces.opt!(Json[]) )
269 					clsdecl.derivedInterfaces ~= parseType(intf, clsdecl);
270 				ret = clsdecl;
271 				break;
272 			case "interface":
273 				auto intfdecl = new InterfaceDeclaration(parent, json.name.get!string);
274 				foreach( intf; json.interfaces.opt!(Json[]) )
275 					intfdecl.derivedInterfaces ~= parseType(intf, intfdecl);
276 				ret = intfdecl;
277 				break;
278 		}
279 
280 		insertIntoTypeMap(ret);
281 
282 		ret.members = parseDeclList(json.members, ret);
283 
284 		return ret;
285 	}
286 
287 	auto parseVariableDecl(Json json, Entity parent)
288 	{
289 		auto ret = new VariableDeclaration(parent, json.name.get!string);
290 		ret.type = parseType(json, ret);
291 		return ret;
292 	}
293 
294 	auto parseTemplateDecl(Json json, Entity parent)
295 	{
296 		auto ret = new TemplateDeclaration(parent, json.name.get!string);
297 		foreach (arg; json.parameters.opt!(Json[])) {
298 			string argstr;
299 			switch (arg.kind.get!string) {
300 				case "value":
301 					if (auto pt = "type" in arg) argstr = pt.get!string ~ ' ';
302 					else argstr = demanglePrettyType(arg.deco.get!string) ~ ' ';
303 					goto default;
304 				case "alias":
305 					argstr = "alias ";
306 					goto default;
307 				case "tuple":
308 					argstr ~= arg.name.get!string ~ "...";
309 					break;
310 				default:
311 					argstr ~= arg.name.get!string;
312 			}
313 			ret.templateArgs ~= new TemplateParameterDeclaration(ret, argstr);
314 		}
315 		ret.members = parseDeclList(json.members, ret);
316 		return ret;
317 	}
318 
319 	Type parseType(Json json, Entity sc, string def_type = "void", bool warn_id_not_exists = true)
320 	{
321 		string str;
322 		if( json.type == Json.Type.Undefined ){
323 			if (warn_id_not_exists) logWarn("No type found for %s.", sc.qualifiedName);
324 			str = def_type;
325 		} else if( json.type == Json.Type.String ) str = json.get!string();
326 		else if( auto pv = "deco" in json ) str = demanglePrettyType(pv.get!string());
327 		else if( auto pv = "type" in json ) str = pv.get!string();
328 		else if( auto pv = "originalType" in json ) str = pv.get!string();
329 
330 		if( str.length == 0 ) str = def_type;
331 
332 		if( !str.length ) return null;
333 
334 		auto tokens = tokenizeDSource(str);
335 		
336 		logDebug("parse type '%s'", str);
337 		try {
338 			auto type = parseTypeDecl(tokens, sc);
339 			type.text = str;
340 			return type;
341 		} catch( Exception e ){
342 			logError("Error parsing type '%s': %s", str, e.msg);
343 			auto type = new Type;
344 			type.text = str;
345 			type.typeName = str;
346 			type.kind = TypeKind.Primitive;
347 			return type;
348 		}
349 	}
350 
351 	Value parseValue(string str)
352 	{
353 		auto ret = new Value;
354 		//ret.type = ;
355 		ret.valueString = str;
356 		return ret;
357 	}
358 
359 	Protection parseProtection(Json prot)
360 	{
361 		switch( prot.opt!string ){
362 			default: return Protection.Public;
363 			case "package": return Protection.Package;
364 			case "protected": return Protection.Protected;
365 			case "private": return Protection.Private;
366 		}
367 	}
368 
369 	Declaration lookupDecl(string qualified_name, Entity sc)
370 	{
371 		while(sc){
372 			auto ent = cast(Declaration)sc.lookup(qualified_name);
373 			if( ent ) return ent;
374 			sc = sc.parent;
375 		}
376 		return null;
377 	}
378 
379 	Type parseTypeDecl(ref string[] tokens, Entity sc)
380 	{
381 
382 		auto ret = parseType(tokens, sc);
383 		return ret;
384 	}
385 
386 	Type parseType(ref string[] tokens, Entity sc)
387 	{
388 		string[] attributes;
389 		auto basic_type = parseBasicType(tokens, sc, attributes);
390 		basic_type.attributes ~= attributes;
391 		return basic_type;	
392 	}
393 
394 	Type parseBasicType(ref string[] tokens, Entity sc, out string[] attributes)
395 	{
396 		static immutable global_attribute_keywords = ["abstract", "auto", "const", "deprecated", "enum",
397 			"extern", "final", "immutable", "inout", "shared", "nothrow", "override", "pure",
398 			"__gshared", "scope", "static", "synchronize"];
399 
400 		static immutable parameter_attribute_keywords = ["auto", "const", "final", "immutable", "in", "inout",
401 			"lazy", "out", "ref", "scope", "shared"];
402 
403 		static immutable member_function_attribute_keywords = ["const", "immutable", "inout", "shared", "pure", "nothrow"];
404 		
405 			
406 		if( tokens.length > 0 && tokens[0] == "extern" ){
407 			enforce(tokens[1] == "(");
408 			enforce(tokens[3] == ")");
409 			attributes ~= join(tokens[0 .. 4]);
410 			tokens = tokens[4 .. $];
411 		}
412 		
413 		immutable string[] attribute_keywords = global_attribute_keywords ~ parameter_attribute_keywords ~ member_function_attribute_keywords;
414 		/*final switch( sc ){
415 			case DeclScope.Global: attribute_keywords = global_attribute_keywords; break;
416 			case DeclScope.Parameter: attribute_keywords = parameter_attribute_keywords; break;
417 			case DeclScope.Class: attribute_keywords = member_function_attribute_keywords; break;
418 		}*/
419 
420 		while( tokens.length > 0 ){
421 			if( tokens.front == "@" ){
422 				tokens.popFront();
423 				attributes ~= "@"~tokens.front;
424 				tokens.popFront();
425 			} else if( attribute_keywords.countUntil(tokens[0]) >= 0 && tokens[1] != "(" ){
426 				attributes ~= tokens.front;
427 				tokens.popFront();
428 			} else break;
429 		}
430 
431 		Type type;
432 		static immutable const_modifiers = ["const", "immutable", "shared", "inout"];
433 		if (tokens.length > 2 && tokens[1] == "(" && const_modifiers.countUntil(tokens[0]) >= 0) {
434 			auto mod = tokens.front;
435 			tokens.popFrontN(2);
436 			string[] subattrs;
437 			type = parseBasicType(tokens, sc, subattrs);
438 			type.modifiers ~= mod;
439 			type.attributes ~= subattrs;
440 			enforce(!tokens.empty && tokens.front == ")", format("Missing ')' for '%s('", mod));
441 			tokens.popFront();
442 		} else {
443 			type = new Type;
444 			type.kind = TypeKind.Primitive;
445 			m_primTypes ~= tuple(type, sc);
446 
447 			size_t start = 0, end;
448 			if( tokens[start] == "." ) start++;
449 			for( end = start; end < tokens.length && isIdent(tokens[end]); ){
450 				end++;
451 				if( end >= tokens.length || tokens[end] != "." )
452 					break;
453 				end++;
454 			}
455 
456 			size_t i = end;
457 
458 			string type_name, nested_name;
459 			if( i == 0 && tokens[0] == "..." ){
460 				type_name = "...";
461 				nested_name = null;
462 			} else if( i == 0 && tokens[0] == "(" ){
463 				type_name = "constructor";
464 				nested_name = null;
465 			} else {
466 				enforce(i > 0, "Expected identifier but got "~tokens.front);
467 				type.typeName = join(tokens[start .. end]);
468 				//type.typeDecl = cast(Declaration)sc.lookup(type.typeName);
469 				tokens.popFrontN(i);
470 				
471 				if (type.typeName == "typeof" && !tokens.empty && tokens.front == "(") {
472 					type.typeName ~= "(";
473 					tokens.popFront();
474 					int level = 1;
475 					while (!tokens.empty && level > 0) {
476 						if (tokens.front == "(") level++;
477 						else if( tokens.front == ")") level--;
478 						type.typeName ~= tokens.front;
479 						tokens.popFront();
480 					}
481 				} else if( !tokens.empty && tokens.front == "!" ){
482 					tokens.popFront();
483 					if( tokens.front == "(" ){
484 						size_t j = 1;
485 						int cc = 1;
486 						while( cc > 0 ){
487 							assert(j < tokens.length);
488 							if( tokens[j] == "(" ) cc++;
489 							else if( tokens[j] == ")") cc--;
490 							j++;
491 						}
492 						type.templateArgs = join(tokens[0 .. j]);
493 						tokens.popFrontN(j);
494 						logDebug("templargs: %s", type.templateArgs);
495 					} else {
496 						type.templateArgs = tokens[0];
497 						tokens.popFront();
498 					}
499 					
500 					// HACK: dropping the actual type name here!
501 					while (!tokens.empty && tokens.front == ".") {
502 						tokens.popFront();
503 						if (!tokens.empty()) tokens.popFront();
504 					}
505 				}
506 			}
507 		}
508 		
509 		while( !tokens.empty ){
510 			if( tokens.front == "*" ){
511 				auto ptr = new Type;
512 				ptr.kind = TypeKind.Pointer;
513 				ptr.elementType = type;
514 				type = ptr;
515 				tokens.popFront();
516 			} else if( tokens.front == "[" ){
517 				tokens.popFront();
518 				if( tokens.front == "]" ){
519 					auto arr = new Type;
520 					arr.kind = TypeKind.Array;
521 					arr.elementType = type;
522 					type = arr;
523 				} else {
524 					string[] tokens_copy = tokens;
525 					Type keytp;
526 					if (!isDigit(tokens.front[0]) && tokens.front != "!") keytp = parseType(tokens_copy, sc);
527 					if (keytp && !tokens_copy.empty && tokens_copy.front == "]") {
528 						tokens = tokens_copy;
529 						logDebug("GOT TYPE: %s", keytp.toString());
530 						auto aa = new Type;
531 						aa.kind = TypeKind.AssociativeArray;
532 						aa.elementType = type;
533 						aa.keyType = keytp;
534 						type = aa;
535 					} else {
536 						auto arr = new Type;
537 						arr.kind = TypeKind.StaticArray;
538 						arr.elementType = type;
539 						arr.arrayLength = tokens.front;
540 						tokens.popFront();
541 						while (!tokens.empty && tokens.front != "]") {
542 							arr.arrayLength ~= tokens.front;
543 							tokens.popFront();
544 						}
545 						type = arr;
546 					}
547 				}
548 				enforce(tokens.front == "]", "Expected ']', got '"~tokens.front~"'.");
549 				tokens.popFront();
550 			} else break;
551 		}
552 		
553 		while (!tokens.empty && (tokens.front == "function" || tokens.front == "delegate" || tokens.front == "(")) {
554 			Type ftype = new Type;
555 			ftype.kind = tokens.front == "(" || tokens.front == "function" ? TypeKind.Function : TypeKind.Delegate;
556 			ftype.returnType = type;
557 			if (tokens.front != "(") tokens.popFront();
558 			enforce(tokens.front == "(");
559 			tokens.popFront();
560 			if (!tokens.empty && tokens.front == ",") tokens.popFront(); // sometimes demangleType() returns something like "void(, ...)"
561 			while (true) {
562 				if (tokens.front == ")") break;
563 				enforce(!tokens.empty);
564 				ftype.parameterTypes ~= parseTypeDecl(tokens, sc);
565 				if (tokens.front != "," && tokens.front != ")") {
566 					ftype._parameterNames ~= tokens.front;
567 					tokens.popFront();
568 				} else ftype._parameterNames ~= null;
569 				if (tokens.front == "...") {
570 					ftype._parameterNames[$-1] ~= tokens.front;
571 					tokens.popFront();
572 				}
573 				if (tokens.front == "=") {
574 					tokens.popFront();
575 					string defval;
576 					int ccount = 0;
577 					while (!tokens.empty) {
578 						if (ccount == 0 && (tokens.front == "," || tokens.front == ")"))
579 							break;
580 						if (tokens.front == "(") ccount++;
581 						else if (tokens.front == ")") ccount--;
582 						defval ~= tokens.front;
583 						tokens.popFront();
584 					}
585 					ftype._parameterDefaultValues ~= parseValue(defval);
586 					logDebug("got defval %s", defval);
587 				} else ftype._parameterDefaultValues ~= null;
588 				if (tokens.front == ")") break;
589 				enforce(tokens.front == ",", "Expecting ',', got "~tokens.front);
590 				tokens.popFront();
591 			}
592 			tokens.popFront();
593 			type = ftype;
594 		}
595 
596 		return type;
597 	}
598 	
599 	string[] tokenizeDSource(string dsource_)
600 	{
601 		static immutable dstring[] tokens = [
602 			"/", "/=", ".", "..", "...", "&", "&=", "&&", "|", "|=", "||",
603 			"-", "-=", "--", "+", "+=", "++", "<", "<=", "<<", "<<=",
604 			"<>", "<>=", ">", ">=", ">>=", ">>>=", ">>", ">>>", "!", "!=",
605 			"!<>", "!<>=", "!<", "!<=", "!>", "!>=", "(", ")", "[", "]",
606 			"{", "}", "?", ",", ";", ":", "$", "=", "==", "*", "*=",
607 			"%", "%=", "^", "^=", "~", "~=", "@", "=>", "#"
608 		];
609 		static bool[dstring] token_map;
610 		
611 		if( !token_map.length ){
612 			foreach( t; tokens )
613 				token_map[t] = true;
614 			token_map.rehash;
615 		}
616 		
617 		dstring dsource = to!dstring(dsource_);
618 		
619 		dstring[] ret;
620 		outer:
621 		while(true){
622 			dsource = stripLeft(dsource);
623 			if( dsource.length == 0 ) break;
624 			
625 			// special token?
626 			foreach_reverse( i; 1 .. min(5, dsource.length+1) )
627 				if( dsource[0 .. i] in token_map ){
628 					ret ~= dsource[0 .. i];
629 					dsource.popFrontN(i);
630 					continue outer;
631 				}
632 			
633 			// identifier?
634 			if( dsource[0] == '_' || std.uni.isAlpha(dsource[0]) ){
635 				size_t i = 1;
636 				while( i < dsource.length && (dsource[i] == '_' || std.uni.isAlpha(dsource[i]) || isDigit(dsource[i])) ) i++;
637 				ret ~= dsource[0 .. i];
638 				dsource.popFrontN(i);
639 				continue;
640 			}
641 			
642 			// character literal?
643 			if( dsource[0] == '\'' ){
644 				size_t i = 1;
645 				while( dsource[i] != '\'' ){
646 					if( dsource[i] == '\\' ) i++;
647 					i++;
648 					enforce(i < dsource.length);
649 				}
650 				ret ~= dsource[0 .. i+1];
651 				dsource.popFrontN(i+1);
652 				continue;
653 			}
654 			
655 			// string? (incomplete!)
656 			if( dsource[0] == '"' ){
657 				size_t i = 1;
658 				while( dsource[i] != '"' ){
659 					if( dsource[i] == '\\' ) i++;
660 					i++;
661 					enforce(i < dsource.length);
662 				}
663 				ret ~= dsource[0 .. i+1];
664 				dsource.popFrontN(i+1);
665 				continue;
666 			}
667 			
668 			// number?
669 			if( isDigit(dsource[0]) || dsource[0] == '.' ){
670 				auto dscopy = dsource;
671 				parse!double(dscopy);
672 				ret ~= dsource[0 .. dsource.length-dscopy.length];
673 				dsource.popFrontN(dsource.length-dscopy.length);
674 				if( dsource.startsWith("u") ) dsource.popFront();
675 				else if( dsource.startsWith("f") ) dsource.popFront();
676 				continue;
677 			}
678 			
679 			ret ~= dsource[0 .. 1];
680 			dsource.popFront();
681 		}
682 		
683 		auto ret_ = new string[ret.length];
684 		foreach( i; 0 .. ret.length ) ret_[i] = to!string(ret[i]);
685 		return ret_;
686 	}
687 
688 	bool isDigit(dchar ch)
689 	{
690 		return ch >= '0' && ch <= '9';
691 	}
692 
693 	bool isIdent(string str)
694 	{
695 		if( str.length < 1 ) return false;
696 		foreach( i, dchar ch; str ){
697 			if( ch == '_' || std.uni.isAlpha(ch) ) continue;
698 			if( i > 0 && isDigit(ch) ) continue;
699 			return false;
700 		}
701 		return true;	
702 	}
703 
704 	string fullStrip(string s)
705 	{
706 		string chars = " \t\r\n";
707 		while( s.length > 0 && chars.countUntil(s[0]) >= 0 ) s.popFront();
708 		while( s.length > 0 && chars.countUntil(s[$-1]) >= 0 ) s.popBack();
709 		return s;
710 	}
711 
712 	void insertIntoTypeMap(Declaration decl)
713 	{
714 		string[] parts = split(decl.qualifiedName, ".");
715 		foreach( i; 0 .. parts.length ){
716 			auto partial_name = join(parts[i .. $], ".");
717 			m_typeMap[partial_name] = decl;
718 		}
719 	}
720 }
721 
722 string demanglePrettyType(string mangled_type)
723 {
724 	auto str = assumeUnique(demangleType(mangled_type));
725 	str = str.replace("immutable(char)[]", "string");
726 	str = str.replace("immutable(wchar)[]", "wstring");
727 	str = str.replace("immutable(dchar)[]", "dstring");
728 	return str;
729 }