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 }