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