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