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 }