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