1 module clang; 2 3 4 import clang.c.index; 5 import clang.c.util: EnumD; 6 7 8 immutable bool[string] gPredefinedCursors; 9 10 version (Windows) 11 extern(C) private int _mktemp_s(char* nameTemplate, size_t sizeInChars) nothrow @safe @nogc; 12 13 shared static this() nothrow { 14 try { 15 16 const fileName = () { 17 import std.file: tempDir; 18 import std.path: buildPath; 19 char[] tmpnamBuf = "libclangXXXXXX\0".dup; 20 version (Posix) { 21 import core.sys.posix.stdlib: mkstemp; 22 mkstemp(tmpnamBuf.ptr); 23 } 24 else version (Windows) 25 _mktemp_s(tmpnamBuf.ptr, tmpnamBuf.length); 26 return tempDir().buildPath(tmpnamBuf[0 .. $-1]); 27 }(); 28 { 29 // create an empty file 30 import std.stdio: File; 31 auto f = File(fileName, "w"); 32 f.writeln; 33 f.flush; 34 f.detach; 35 } 36 37 auto tu = parse(fileName, 38 ["-xc"], 39 TranslationUnitFlags.DetailedPreprocessingRecord); 40 foreach(cursor; tu.cursor.children) { 41 gPredefinedCursors[cursor.spelling] = true; 42 } 43 44 } catch(Exception e) { 45 import std.stdio: stderr; 46 try 47 stderr.writeln("Error initialising libclang: ", e); 48 catch(Exception _) {} 49 } 50 } 51 52 mixin EnumD!("TranslationUnitFlags", CXTranslationUnit_Flags, "CXTranslationUnit_"); 53 mixin EnumD!("Language", CXLanguageKind, "CXLanguage_"); 54 55 56 TranslationUnit parse(in string fileName, 57 in TranslationUnitFlags translUnitflags = TranslationUnitFlags.None) 58 @safe 59 { 60 return parse(fileName, [], translUnitflags); 61 } 62 63 64 mixin EnumD!("ErrorCode", CXErrorCode, ""); 65 mixin EnumD!("DiagnosticSeverity", CXDiagnosticSeverity, "CXDiagnostic_"); 66 mixin EnumD!("TemplateArgumentKind", CXTemplateArgumentKind, "CXTemplateArgumentKind_"); 67 68 69 TranslationUnit parse(in string fileName, 70 in string[] commandLineArgs, 71 in TranslationUnitFlags translUnitflags = TranslationUnitFlags.None) 72 @safe 73 { 74 75 import std.string: toStringz; 76 import std.algorithm: map; 77 import std.array: array, join; 78 import std.conv: text; 79 80 // faux booleans 81 const excludeDeclarationsFromPCH = 0; 82 const displayDiagnostics = 0; 83 auto index = clang_createIndex(excludeDeclarationsFromPCH, displayDiagnostics); 84 CXUnsavedFile[] unsavedFiles; 85 const commandLineArgz = commandLineArgs 86 .map!(a => a.toStringz) 87 .array; 88 89 CXTranslationUnit cx; 90 const err = () @trusted { 91 return cast(ErrorCode)clang_parseTranslationUnit2( 92 index, 93 fileName.toStringz, 94 commandLineArgz.ptr, // .ptr since the length can be 0 95 cast(int)commandLineArgz.length, 96 unsavedFiles.ptr, // .ptr since the length can be 0 97 cast(uint)unsavedFiles.length, 98 translUnitflags, 99 &cx, 100 ); 101 }(); 102 103 if(err != ErrorCode.success) { 104 throw new Exception(text("Could not parse ", fileName, ": ", err)); 105 } 106 107 string[] errorMessages; 108 // throw if there are error diagnostics 109 foreach(i; 0 .. clang_getNumDiagnostics(cx)) { 110 auto diagnostic = clang_getDiagnostic(cx, i); 111 scope(exit) clang_disposeDiagnostic(diagnostic); 112 const severity = cast(DiagnosticSeverity) clang_getDiagnosticSeverity(diagnostic); 113 enum diagnosticOptions = CXDiagnostic_DisplaySourceLocation | CXDiagnostic_DisplayColumn; 114 if(severity == DiagnosticSeverity.Error || severity == DiagnosticSeverity.Fatal) 115 errorMessages ~= clang_formatDiagnostic(diagnostic, diagnosticOptions).toString; 116 } 117 118 if(errorMessages.length > 0) 119 throw new Exception(text("Error parsing '", fileName, "':\n", 120 errorMessages.join("\n"))); 121 122 123 return TranslationUnit(cx); 124 } 125 126 string[] systemPaths() @safe { 127 import std.process: execute; 128 import std.string: splitLines, stripLeft; 129 import std.algorithm: map, countUntil; 130 import std.array: array; 131 132 version(Windows) 133 { 134 enum devnull = "NUL"; 135 } else { 136 enum devnull = "/dev/null"; 137 } 138 139 const res = () { 140 try 141 { 142 return execute(["clang", "-v", "-xc++", devnull, "-fsyntax-only"], ["LANG": "C"]); 143 } 144 catch (Exception e) 145 { 146 import std.typecons : Tuple; 147 return Tuple!(int, "status", string, "output")(-1, e.msg); 148 } 149 }(); 150 if(res.status != 0) throw new Exception("Failed to call clang:\n" ~ res.output); 151 152 auto lines = res.output.splitLines; 153 154 const startIndex = lines.countUntil("#include <...> search starts here:") + 1; 155 assert(startIndex > 0); 156 const endIndex = lines.countUntil("End of search list."); 157 assert(endIndex > 0); 158 159 return lines[startIndex .. endIndex].map!stripLeft.array; 160 } 161 162 163 mixin EnumD!("ChildVisitResult", CXChildVisitResult, "CXChildVisit_"); 164 alias CursorVisitor = ChildVisitResult delegate(Cursor cursor, Cursor parent); 165 166 struct TranslationUnit { 167 168 CXTranslationUnit cx; 169 Cursor cursor; 170 171 this(CXTranslationUnit cx) @safe nothrow { 172 this.cx = cx; 173 this.cursor = Cursor(clang_getTranslationUnitCursor(cx)); 174 } 175 } 176 177 string toString(CXString cxString) @safe pure nothrow { 178 import std.conv: to; 179 auto cstr = clang_getCString(cxString); 180 scope(exit) clang_disposeString(cxString); 181 return () @trusted { return cstr.to!string; }(); 182 } 183 184 string[] toStrings(CXStringSet* strings) @trusted pure nothrow { 185 scope(exit) clang_disposeStringSet(strings); 186 string[] ret; 187 foreach(cxstr; strings.Strings[0 .. strings.Count]) 188 ret ~= cxstr.toString; 189 return ret; 190 } 191 192 mixin EnumD!("AccessSpecifier", CX_CXXAccessSpecifier, "CX_CXX"); 193 194 195 struct Cursor { 196 197 import std.traits: ReturnType; 198 199 mixin EnumD!("Kind", CXCursorKind, "CXCursor_"); 200 mixin EnumD!("StorageClass", CX_StorageClass, "CX_SC_"); 201 202 alias Hash = ReturnType!(clang_hashCursor); 203 204 CXCursor cx; 205 private Cursor[] _children; 206 Kind kind; 207 string spelling; 208 Type type; 209 Type underlyingType; 210 SourceRange sourceRange; 211 212 this(CXCursor cx) @safe pure nothrow { 213 this.cx = cx; 214 kind = cast(Kind) clang_getCursorKind(cx); 215 spelling = clang_getCursorSpelling(cx).toString; 216 type = Type(clang_getCursorType(cx)); 217 218 if(kind == Cursor.Kind.TypedefDecl || kind == Cursor.Kind.TypeAliasDecl) 219 underlyingType = Type(clang_getTypedefDeclUnderlyingType(cx)); 220 221 sourceRange = SourceRange(clang_getCursorExtent(cx)); 222 } 223 224 private static extern(C) CXChildVisitResult ctorVisitor(CXCursor cursor, 225 CXCursor parent, 226 void* clientData_) 227 @safe nothrow 228 { 229 auto children = () @trusted { return cast(Cursor[]*) clientData_; }(); 230 *children ~= Cursor(cursor); 231 return CXChildVisit_Continue; 232 } 233 234 this(in Kind kind, in string spelling) @safe @nogc pure nothrow { 235 this(kind, spelling, Type()); 236 } 237 238 this(in Kind kind, in string spelling, Type type) @safe @nogc pure nothrow { 239 this.kind = kind; 240 this.spelling = spelling; 241 this.type = type; 242 } 243 244 /// Lazily return the cursor's children 245 inout(Cursor)[] children() @safe @property nothrow inout { 246 if(_children.length) return _children; 247 248 inout(Cursor)[] ret; 249 // calling Cursor.visitChildren here would cause infinite recursion 250 // because cvisitor constructs a Cursor out of the parent 251 () @trusted { clang_visitChildren(cx, &ctorVisitor, &ret); }(); 252 return ret; 253 } 254 255 void children(Cursor[] cursors) @safe @property pure nothrow { 256 _children = cursors; 257 } 258 259 Type returnType() @safe pure nothrow const { 260 return Type(clang_getCursorResultType(cx)); 261 } 262 263 /** 264 For EnumConstantDecl cursors, return the numeric value 265 */ 266 auto enumConstantValue() @safe @nogc pure nothrow const { 267 assert(kind == Cursor.Kind.EnumConstantDecl); 268 return clang_getEnumConstantDeclValue(cx); 269 } 270 271 Language language() @safe @nogc pure nothrow const { 272 return cast(Language) clang_getCursorLanguage(cx); 273 } 274 275 Cursor canonical() @safe nothrow const { 276 return Cursor(clang_getCanonicalCursor(cx)); 277 } 278 279 /** 280 If this is the canonical cursor. Given forward declarations, there may 281 be several cursors for one entity. This returns true if this cursor 282 is the canonical one. 283 */ 284 bool isCanonical() @safe @nogc pure nothrow const { 285 return cast(bool) clang_equalCursors(cx, clang_getCanonicalCursor(cx)); 286 } 287 288 bool isDefinition() @safe @nogc pure nothrow const { 289 return cast(bool) clang_isCursorDefinition(cx); 290 } 291 292 bool isNull() @safe @nogc pure nothrow const { 293 return cast(bool) clang_Cursor_isNull(cx); 294 } 295 296 static Cursor nullCursor() @safe nothrow { 297 return Cursor(clang_getNullCursor()); 298 } 299 300 Cursor definition() @safe nothrow const { 301 return Cursor(clang_getCursorDefinition(cx)); 302 } 303 304 string toString() @safe pure nothrow const { 305 import std.conv: text; 306 try { 307 const returnTypeStr = kind == Kind.FunctionDecl 308 ? text(", ", returnType) 309 : ""; 310 311 return text("Cursor(", kind, `, "`, spelling, `", `, type, returnTypeStr, ")"); 312 } catch(Exception e) 313 assert(false, "Fatal error in Cursor.toString: " ~ e.msg); 314 } 315 316 bool isPredefined() @safe @nogc pure nothrow const { 317 return (spelling in gPredefinedCursors) !is null; 318 } 319 320 Cursor semanticParent() @safe nothrow const { 321 return Cursor(clang_getCursorSemanticParent(cx)); 322 } 323 324 Cursor lexicalParent() @safe nothrow const { 325 return Cursor(clang_getCursorLexicalParent(cx)); 326 } 327 328 bool isInvalid() @safe @nogc pure nothrow const { 329 return cast(bool) clang_isInvalid(cx.kind); 330 } 331 332 auto hash() @safe @nogc pure nothrow const { 333 return clang_hashCursor(cx); 334 } 335 336 string mangling() @safe pure nothrow const { 337 return clang_Cursor_getMangling(cx).toString; 338 } 339 340 bool isAnonymous() @safe @nogc pure nothrow const { 341 return cast(bool) clang_Cursor_isAnonymous(cx); 342 } 343 344 bool isBitField() @safe @nogc pure nothrow const { 345 return cast(bool) clang_Cursor_isBitField(cx); 346 } 347 348 int bitWidth() @safe @nogc pure nothrow const { 349 return clang_getFieldDeclBitWidth(cx); 350 } 351 352 auto accessSpecifier() @safe @nogc pure nothrow const { 353 return cast(AccessSpecifier) clang_getCXXAccessSpecifier(cx); 354 } 355 356 StorageClass storageClass() @safe @nogc pure nothrow const { 357 return cast(StorageClass) clang_Cursor_getStorageClass(cx); 358 } 359 360 bool isConstCppMethod() @safe @nogc pure nothrow const { 361 return cast(bool) clang_CXXMethod_isConst(cx); 362 } 363 364 bool isMoveConstructor() @safe @nogc pure nothrow const { 365 return cast(bool) clang_CXXConstructor_isMoveConstructor(cx); 366 } 367 368 bool isCopyConstructor() @safe @nogc pure nothrow const { 369 return cast(bool) clang_CXXConstructor_isCopyConstructor(cx); 370 } 371 372 bool isMacroFunction() @safe @nogc pure nothrow const { 373 return cast(bool) clang_Cursor_isMacroFunctionLike(cx); 374 } 375 376 bool isMacroBuiltin() @safe @nogc pure nothrow const { 377 return cast(bool) clang_Cursor_isMacroBuiltin(cx); 378 } 379 380 Cursor specializedCursorTemplate() @safe pure nothrow const { 381 return Cursor(clang_getSpecializedCursorTemplate(cx)); 382 } 383 384 TranslationUnit translationUnit() @safe nothrow const { 385 return TranslationUnit(clang_Cursor_getTranslationUnit(cx)); 386 } 387 388 Token[] tokens() @safe nothrow const { 389 import std.algorithm: map; 390 import std.array: array; 391 392 CXToken* tokens; 393 uint numTokens; 394 395 () @trusted { clang_tokenize(translationUnit.cx, sourceRange.cx, &tokens, &numTokens); }(); 396 // I hope this only deallocates the array 397 scope(exit) clang_disposeTokens(translationUnit.cx, tokens, numTokens); 398 399 auto tokenSlice = () @trusted { return tokens[0 .. numTokens]; }(); 400 401 return tokenSlice.map!(a => Token(a, translationUnit)).array; 402 } 403 404 alias templateParams = templateParameters; 405 406 const(Cursor)[] templateParameters() @safe nothrow const { 407 import std.algorithm: filter; 408 import std.array: array; 409 410 const amTemplate = 411 kind == Cursor.Kind.ClassTemplate 412 || kind == Cursor.Kind.TypeAliasTemplateDecl 413 || kind == Cursor.Kind.FunctionTemplate 414 ; 415 const templateCursor = amTemplate ? this : specializedCursorTemplate; 416 417 auto range = templateCursor 418 .children 419 .filter!(a => a.kind == Cursor.Kind.TemplateTypeParameter || 420 a.kind == Cursor.Kind.NonTypeTemplateParameter); 421 422 // Why is this @system? Who knows. 423 return () @trusted { return range.array; }(); 424 } 425 426 /** 427 If declared at file scope. 428 */ 429 bool isFileScope() @safe nothrow const { 430 return lexicalParent.kind == Cursor.Kind.TranslationUnit; 431 } 432 433 int numTemplateArguments() @safe @nogc pure nothrow const { 434 return clang_Cursor_getNumTemplateArguments(cx); 435 } 436 437 TemplateArgumentKind templateArgumentKind(int i) @safe @nogc pure nothrow const { 438 return cast(TemplateArgumentKind) clang_Cursor_getTemplateArgumentKind(cx, i); 439 } 440 441 Type templateArgumentType(int i) @safe pure nothrow const { 442 return Type(clang_Cursor_getTemplateArgumentType(cx, i)); 443 } 444 445 long templateArgumentValue(int i) @safe @nogc pure nothrow const { 446 return clang_Cursor_getTemplateArgumentValue(cx, i); 447 } 448 449 bool isVirtual() @safe @nogc pure nothrow const { 450 return cast(bool) clang_CXXMethod_isVirtual(cx); 451 } 452 453 bool isPureVirtual() @safe @nogc pure nothrow const { 454 return cast(bool) clang_CXXMethod_isPureVirtual(cx); 455 } 456 457 string displayName() @safe pure nothrow const { 458 return clang_getCursorDisplayName(cx).toString; 459 } 460 461 /** 462 For e.g. TypeRef or TemplateRef 463 */ 464 Cursor referencedCursor() @safe nothrow const { 465 return Cursor(clang_getCursorReferenced(cx)); 466 } 467 468 Cursor[] overriddenCursors() @trusted /* @safe with DIP1000 */ const { 469 import std.algorithm: map; 470 import std.array: array; 471 472 uint length; 473 CXCursor* cursors; 474 475 clang_getOverriddenCursors(cx, &cursors, &length); 476 scope(exit) clang_disposeOverriddenCursors(cursors); 477 478 return cursors[0 .. length].map!(a => Cursor(a)).array; 479 } 480 481 auto numOverloadedDecls() @safe @nogc pure nothrow const { 482 return clang_getNumOverloadedDecls(cx); 483 } 484 485 Cursor overloadedDecl(int i) @safe nothrow const { 486 return Cursor(clang_getOverloadedDecl(cx, cast(uint) i)); 487 } 488 489 bool opEquals(ref const(Cursor) other) @safe @nogc pure nothrow const { 490 return cast(bool) clang_equalCursors(cx, other.cx); 491 } 492 493 bool opEquals(in Cursor other) @safe @nogc pure nothrow const { 494 return cast(bool) clang_equalCursors(cx, other.cx); 495 } 496 497 void visitChildren(scope CursorVisitor visitor) @safe nothrow const { 498 scope clientData = ClientData(visitor); 499 // why isn't this @safe with dip10000??? 500 () @trusted { clang_visitChildren(cx, &cvisitor, &clientData); }(); 501 } 502 503 int opApply(scope int delegate(Cursor cursor, Cursor parent) @safe block) @safe nothrow const { 504 return opApplyN(block); 505 } 506 507 int opApply(scope int delegate(Cursor cursor) @safe block) @safe nothrow const { 508 return opApplyN(block); 509 } 510 511 private int opApplyN(T)(scope T block) const { 512 import std.traits: Parameters; 513 514 int stop = 0; 515 516 enum numParams = Parameters!T.length; 517 518 visitChildren((cursor, parent) { 519 520 static if(numParams == 2) 521 stop = block(cursor, parent); 522 else static if(numParams == 1) 523 stop = block(cursor); 524 else 525 static assert(false); 526 527 return stop 528 ? ChildVisitResult.Break 529 : ChildVisitResult.Continue; 530 }); 531 532 return stop; 533 } 534 } 535 536 537 struct SourceRange { 538 539 CXSourceRange cx; 540 string path; 541 SourceLocation start; 542 SourceLocation end; 543 544 this(CXSourceRange cx) @safe pure nothrow { 545 this.cx = cx; 546 this.start = clang_getRangeStart(cx); 547 this.end = clang_getRangeEnd(cx); 548 this.path = start.path; 549 } 550 551 string toString() @safe pure const { 552 import std.conv: text; 553 return text(`SourceRange("`, start.path, `", `, start.line, ":", start.column, ", ", end.line, ":", end.column, ")"); 554 } 555 } 556 557 struct SourceLocation { 558 CXSourceLocation cx; 559 string path; 560 uint line; 561 uint column; 562 uint offset; 563 564 this(CXSourceLocation cx) @safe pure nothrow { 565 this.cx = cx; 566 567 CXFile file; 568 () @trusted { clang_getExpansionLocation(cx, &file, null, null, null); }(); 569 this.path = clang_getFileName(file).toString; 570 571 () @trusted { clang_getSpellingLocation(cx, &file, &line, &column, &offset); }(); 572 } 573 574 int opCmp(ref const(SourceLocation) other) @safe @nogc pure nothrow const { 575 if(path == other.path && line == other.line && column == other.column && 576 offset == other.offset) 577 return 0; 578 579 if(path < other.path) return -1; 580 if(path > other.path) return 1; 581 if(line < other.line) return -1; 582 if(line > other.line) return 1; 583 if(column < other.column) return -1; 584 if(column > other.column) return 1; 585 if(offset < other.offset) return -1; 586 if(offset > other.offset) return 1; 587 assert(false); 588 } 589 590 string toString() @safe pure nothrow const { 591 import std.conv: text; 592 return text(`"`, path, `" `, line, ":", column, ":", offset); 593 } 594 } 595 596 private struct ClientData { 597 /** 598 The D visitor delegate 599 */ 600 CursorVisitor dvisitor; 601 } 602 603 // This is the C function actually passed to libclang's clang_visitChildren 604 // The context (clientData) contains the D delegate that's then called on the 605 // (cursor, parent) pair 606 private extern(C) CXChildVisitResult cvisitor(CXCursor cursor, CXCursor parent, void* clientData_) { 607 auto clientData = cast(ClientData*) clientData_; 608 return cast(CXChildVisitResult) clientData.dvisitor(Cursor(cursor), Cursor(parent)); 609 } 610 611 612 struct Type { 613 614 mixin EnumD!("Kind", CXTypeKind, "CXType_"); 615 616 CXType cx; 617 Kind kind; 618 string spelling; 619 620 this(CXType cx) @safe pure nothrow { 621 this.cx = cx; 622 this.kind = cast(Kind) cx.kind; 623 spelling = clang_getTypeSpelling(cx).toString; 624 } 625 626 this(in Type other) @trusted pure nothrow { 627 import std.algorithm: map; 628 import std.array: array; 629 630 this.cx.kind = other.cx.kind; 631 this.cx.data[] = other.cx.data[].map!(a => cast(void*) a).array; 632 633 this(this.cx); 634 } 635 636 this(in Kind kind) @safe @nogc pure nothrow { 637 this(kind, ""); 638 } 639 640 this(in Kind kind, in string spelling) @safe @nogc pure nothrow { 641 this.kind = kind; 642 this.spelling = spelling; 643 } 644 645 Type pointee() @safe pure nothrow const { 646 return Type(clang_getPointeeType(cx)); 647 } 648 649 Type unelaborate() @safe nothrow const { 650 return Type(clang_Type_getNamedType(cx)); 651 } 652 653 Type canonical() @safe pure nothrow const { 654 return Type(clang_getCanonicalType(cx)); 655 } 656 657 Type returnType() @safe pure const { 658 return Type(clang_getResultType(cx)); 659 } 660 661 // Returns a range of Type 662 auto paramTypes()() @safe pure const nothrow { 663 664 static struct Range { 665 const CXType cx; 666 const int numArgs; 667 int index = 0; 668 669 bool empty() { 670 return index < 0 || index >= numArgs; 671 } 672 673 void popFront() { 674 ++index; 675 } 676 677 Type front() { 678 return Type(clang_getArgType(cx, index)); 679 } 680 } 681 682 return Range(cx, clang_getNumArgTypes(cx)); 683 } 684 685 bool isVariadicFunction() @safe @nogc pure nothrow const { 686 return cast(bool) clang_isFunctionTypeVariadic(cx); 687 } 688 689 Type elementType() @safe pure nothrow const { 690 return Type(clang_getElementType(cx)); 691 } 692 693 long numElements() @safe @nogc pure nothrow const { 694 return clang_getNumElements(cx); 695 } 696 697 long arraySize() @safe @nogc pure nothrow const { 698 return clang_getArraySize(cx); 699 } 700 701 bool isConstQualified() @safe @nogc pure nothrow const { 702 return cast(bool) clang_isConstQualifiedType(cx); 703 } 704 705 bool isVolatileQualified() @safe @nogc pure nothrow const { 706 return cast(bool) clang_isVolatileQualifiedType(cx); 707 } 708 709 Cursor declaration() @safe pure nothrow const { 710 return Cursor(clang_getTypeDeclaration(cx)); 711 } 712 713 Type namedType() @safe pure nothrow const { 714 return Type(clang_Type_getNamedType(cx)); 715 } 716 717 bool opEquals(ref const(Type) other) @safe @nogc pure nothrow const { 718 return cast(bool) clang_equalTypes(cx, other.cx); 719 } 720 721 bool opEquals(in Type other) @safe @nogc pure nothrow const { 722 return cast(bool) clang_equalTypes(cx, other.cx); 723 } 724 725 bool isInvalid() @safe @nogc pure nothrow const { 726 return kind == Kind.Invalid; 727 } 728 729 long getSizeof() @safe @nogc pure nothrow const { 730 return clang_Type_getSizeOf(cx); 731 } 732 733 int numTemplateArguments() @safe @nogc pure nothrow const { 734 return clang_Type_getNumTemplateArguments(cx); 735 } 736 737 Type typeTemplateArgument(int i) @safe pure nothrow const { 738 return Type(clang_Type_getTemplateArgumentAsType(cx, i)); 739 } 740 741 string toString() @safe pure nothrow const { 742 import std.conv: text; 743 744 try { 745 return text("Type(", kind, `, "`, spelling, `")`); 746 } catch(Exception e) 747 assert(false, "Fatal error in Type.toString: " ~ e.msg); 748 } 749 } 750 751 752 struct Token { 753 754 mixin EnumD!("Kind", CXTokenKind, "CXToken_"); 755 756 Kind kind; 757 string spelling; 758 CXToken cx; 759 TranslationUnit translationUnit; 760 761 this(CXToken cx, TranslationUnit unit) @safe pure nothrow { 762 this.cx = cx; 763 this.translationUnit = unit; 764 this.kind = cast(Kind) clang_getTokenKind(cx); 765 this.spelling = .toString(clang_getTokenSpelling(translationUnit.cx, cx)); 766 } 767 768 this(Kind kind, string spelling) @safe @nogc pure nothrow { 769 this.kind = kind; 770 this.spelling = spelling; 771 } 772 773 string toString() @safe pure const { 774 import std.conv: text; 775 776 return text("Token(", kind, `, "`, spelling, `")`); 777 } 778 779 bool opEquals(in Token other) @safe pure nothrow const { 780 return kind == other.kind && spelling == other.spelling; 781 } 782 }