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, in TranslationUnitFlags translUnitflags)
10     @safe
11 {
12     return parse(fileName, [], translUnitflags);
13 }
14 
15 
16 mixin EnumD!("ErrorCode", CXErrorCode, "");
17 mixin EnumD!("DiagnosticSeverity", CXDiagnosticSeverity, "CXDiagnostic_");
18 
19 
20 TranslationUnit parse(in string fileName, in string[] commandLineArgs, in TranslationUnitFlags translUnitflags)
21     @safe
22 {
23 
24     import std..string: toStringz;
25     import std.algorithm: map;
26     import std.array: array, join;
27     import std.conv: text;
28 
29     // faux booleans
30     const excludeDeclarationsFromPCH = 0;
31     const displayDiagnostics = 0;
32     auto index = clang_createIndex(excludeDeclarationsFromPCH, displayDiagnostics);
33     CXUnsavedFile[] unsavedFiles;
34     const commandLineArgz = commandLineArgs
35         .map!(a => a.toStringz)
36         .array;
37 
38     CXTranslationUnit cx;
39     const err = () @trusted {
40         return cast(ErrorCode)clang_parseTranslationUnit2(
41             index,
42             fileName.toStringz,
43             commandLineArgz.ptr, // .ptr since the length can be 0
44             cast(int)commandLineArgz.length,
45             unsavedFiles.ptr,  // .ptr since the length can be 0
46             cast(uint)unsavedFiles.length,
47             translUnitflags,
48             &cx,
49         );
50     }();
51 
52     if(err != ErrorCode.success) {
53         throw new Exception(text("Could not parse ", fileName, ": ", err));
54     }
55 
56     string[] errorMessages;
57     // throw if there are error diagnostics
58     foreach(i; 0 .. clang_getNumDiagnostics(cx)) {
59         auto diagnostic = clang_getDiagnostic(cx, i);
60         scope(exit) clang_disposeDiagnostic(diagnostic);
61         const severity = cast(DiagnosticSeverity) clang_getDiagnosticSeverity(diagnostic);
62         if(severity == DiagnosticSeverity.Error || severity == DiagnosticSeverity.Fatal)
63             errorMessages ~= clang_formatDiagnostic(diagnostic, 0).toString;
64     }
65 
66     if(errorMessages.length > 0)
67         throw new Exception(text("Error parsing '", fileName, "':\n",
68                                  errorMessages.join("\n")));
69 
70 
71     return TranslationUnit(cx);
72 }
73 
74 string[] systemPaths() @safe {
75     import std.process: execute;
76     import std..string: splitLines, stripLeft;
77     import std.algorithm: map, countUntil;
78     import std.array: array;
79 
80     const res = execute(["gcc", "-v", "-xc", "/dev/null", "-fsyntax-only"], ["LANG": "C"]);
81     if(res.status != 0) throw new Exception("Failed to call gcc:\n" ~ res.output);
82 
83     auto lines = res.output.splitLines;
84 
85     const startIndex = lines.countUntil("#include <...> search starts here:") + 1;
86     assert(startIndex > 0);
87     const endIndex = lines.countUntil("End of search list.");
88     assert(endIndex > 0);
89 
90     return lines[startIndex .. endIndex].map!stripLeft.array;
91 }
92 
93 
94 mixin EnumD!("ChildVisitResult", CXChildVisitResult, "CXChildVisit_");
95 alias CursorVisitor = ChildVisitResult delegate(Cursor cursor, Cursor parent);
96 
97 struct TranslationUnit {
98 
99     CXTranslationUnit cx;
100     Cursor cursor;
101 
102     this(CXTranslationUnit cx) @safe nothrow {
103         this.cx = cx;
104         this.cursor = Cursor(clang_getTranslationUnitCursor(cx));
105     }
106 }
107 
108 string toString(CXString cxString) @safe pure nothrow {
109     import std.conv: to;
110     auto cstr = clang_getCString(cxString);
111     scope(exit) clang_disposeString(cxString);
112     return () @trusted { return cstr.to!string; }();
113 }
114 
115 mixin EnumD!("AccessSpecifier", CX_CXXAccessSpecifier, "CX_CXX");
116 
117 
118 struct Cursor {
119 
120     mixin EnumD!("Kind", CXCursorKind, "CXCursor_");
121 
122     CXCursor cx;
123     private Cursor[] _children;
124     Kind kind;
125     string spelling;
126     Type type;
127     SourceRange sourceRange;
128 
129     this(CXCursor cx) @safe nothrow {
130         this.cx = cx;
131         kind = cast(Kind) clang_getCursorKind(cx);
132         spelling = clang_getCursorSpelling(cx).toString;
133         type = Type(clang_getCursorType(cx));
134         sourceRange = SourceRange(clang_getCursorExtent(cx));
135     }
136 
137     private static extern(C) CXChildVisitResult ctorVisitor(CXCursor cursor,
138                                                             CXCursor parent,
139                                                             void* clientData_)
140         @safe nothrow
141     {
142         auto children = () @trusted { return cast(Cursor[]*) clientData_; }();
143         *children ~= Cursor(cursor);
144         return CXChildVisit_Continue;
145     }
146 
147     this(in Kind kind, in string spelling) @safe @nogc pure nothrow {
148         this(kind, spelling, Type());
149     }
150 
151     this(in Kind kind, in string spelling, Type type) @safe @nogc pure nothrow {
152         this.kind = kind;
153         this.spelling = spelling;
154         this.type = type;
155     }
156 
157     /// Lazily return the cursor's children
158     inout(Cursor)[] children() @safe @property nothrow inout {
159         if(_children.length) return _children;
160 
161         inout(Cursor)[] ret;
162         // calling Cursor.visitChildren here would cause infinite recursion
163         // because cvisitor constructs a Cursor out of the parent
164         () @trusted { clang_visitChildren(cx, &ctorVisitor, &ret); }();
165         return ret;
166     }
167 
168     void children(Cursor[] cursors) @safe @property pure nothrow {
169         _children = cursors;
170     }
171 
172     Type returnType() @safe pure nothrow const {
173         return Type(clang_getCursorResultType(cx));
174     }
175 
176     /**
177        For TypedefDecl cursors, return the underlying type
178      */
179     Type underlyingType() @safe pure nothrow const {
180         assert(kind == Cursor.Kind.TypedefDecl || kind == Cursor.Kind.TypeAliasDecl,
181                "Not a TypedefDecl cursor");
182         return Type(clang_getTypedefDeclUnderlyingType(cx));
183     }
184 
185     /**
186        For EnumConstantDecl cursors, return the numeric value
187      */
188     auto enumConstantValue() @safe @nogc pure nothrow const {
189         assert(kind == Cursor.Kind.EnumConstantDecl);
190         return clang_getEnumConstantDeclValue(cx);
191     }
192 
193     Language language() @safe @nogc pure nothrow const {
194         return cast(Language) clang_getCursorLanguage(cx);
195     }
196 
197     Cursor canonical() @safe nothrow const {
198         return Cursor(clang_getCanonicalCursor(cx));
199     }
200 
201     /**
202        If this is the canonical cursor. Given forward declarations, there may
203        be several cursors for one entity. This returns true if this cursor
204        is the canonical one.
205      */
206     bool isCanonical() @safe @nogc pure nothrow const {
207         return cast(bool) clang_equalCursors(cx, clang_getCanonicalCursor(cx));
208     }
209 
210     bool isDefinition() @safe @nogc pure nothrow const {
211         return cast(bool) clang_isCursorDefinition(cx);
212     }
213 
214     bool isNull() @safe @nogc pure nothrow const {
215         return cast(bool) clang_Cursor_isNull(cx);
216     }
217 
218     static Cursor nullCursor() @safe nothrow {
219         return Cursor(clang_getNullCursor());
220     }
221 
222     Cursor definition() @safe nothrow const {
223         return Cursor(clang_getCursorDefinition(cx));
224     }
225 
226     string toString() @safe pure nothrow const {
227         import std.conv: text;
228         try {
229             const returnTypeStr = kind == Kind.FunctionDecl
230                 ? text(", ", returnType)
231                 : "";
232 
233             return text("Cursor(", kind, `, "`, spelling, `", `, type, returnTypeStr, ")");
234         } catch(Exception e)
235             assert(false, "Fatal error in Cursor.toString: " ~ e.msg);
236     }
237 
238     bool isPredefined() @safe @nogc pure nothrow const {
239         // FIXME
240         return false;
241     }
242 
243     Cursor semanticParent() @safe nothrow const {
244         return Cursor(clang_getCursorSemanticParent(cx));
245     }
246 
247     Cursor lexicalParent() @safe nothrow const {
248         return Cursor(clang_getCursorLexicalParent(cx));
249     }
250 
251     bool isInvalid() @safe @nogc pure nothrow const {
252         return cast(bool) clang_isInvalid(cx.kind);
253     }
254 
255     auto hash() @safe @nogc pure nothrow const {
256         return clang_hashCursor(cx);
257     }
258 
259     string mangling() @safe pure nothrow const {
260         return clang_Cursor_getMangling(cx).toString;
261     }
262 
263     bool isAnonymous() @safe @nogc pure nothrow const {
264         return cast(bool) clang_Cursor_isAnonymous(cx);
265     }
266 
267     bool isBitField() @safe @nogc pure nothrow const {
268         return cast(bool) clang_Cursor_isBitField(cx);
269     }
270 
271     int bitWidth() @safe @nogc pure nothrow const {
272         return clang_getFieldDeclBitWidth(cx);
273     }
274 
275     auto accessSpecifier() @safe @nogc pure nothrow const {
276         return cast(AccessSpecifier) clang_getCXXAccessSpecifier(cx);
277     }
278 
279     bool opEquals(ref const(Cursor) other) @safe @nogc pure nothrow const {
280         return cast(bool) clang_equalCursors(cx, other.cx);
281     }
282 
283     bool opEquals(in Cursor other) @safe @nogc pure nothrow const {
284         return cast(bool) clang_equalCursors(cx, other.cx);
285     }
286 
287     void visitChildren(CursorVisitor visitor) @safe nothrow const {
288         clang_visitChildren(cx, &cvisitor, new ClientData(visitor));
289     }
290 
291     int opApply(scope int delegate(Cursor cursor, Cursor parent) @safe block) @safe nothrow const {
292         return opApplyN(block);
293     }
294 
295     int opApply(scope int delegate(Cursor cursor) @safe block) @safe nothrow const {
296         return opApplyN(block);
297     }
298 
299     private int opApplyN(T...)(int delegate(T args) @safe block) const {
300         int stop = 0;
301 
302         visitChildren((cursor, parent) {
303 
304             static if(T.length == 2)
305                 stop = block(cursor, parent);
306             else static if(T.length == 1)
307                 stop = block(cursor);
308             else
309                 static assert(false);
310 
311             return stop
312                 ? ChildVisitResult.Break
313                 : ChildVisitResult.Continue;
314         });
315 
316         return stop;
317     }
318 }
319 
320 
321 struct SourceRange {
322     CXSourceRange cx;
323     string path;
324     SourceLocation start;
325     SourceLocation end;
326 
327     this(CXSourceRange cx) @safe pure nothrow {
328         this.cx = cx;
329         this.start = clang_getRangeStart(cx);
330         this.end = clang_getRangeEnd(cx);
331         this.path = start.path;
332     }
333 
334     string toString() @safe pure const {
335         import std.conv: text;
336         return text(`SourceRange("`, start.path, `", `, start.line, ":", start.column, ", ", end.line, ":", end.column, ")");
337     }
338 }
339 
340 struct SourceLocation {
341     CXSourceLocation cx;
342     string path;
343     uint line;
344     uint column;
345     uint offset;
346 
347     this(CXSourceLocation cx) @safe pure nothrow {
348         this.cx = cx;
349 
350         CXFile file;
351         () @trusted { clang_getExpansionLocation(cx, &file, null, null, null); }();
352         this.path = clang_getFileName(file).toString;
353 
354         () @trusted { clang_getSpellingLocation(cx, &file, &line, &column, &offset); }();
355     }
356 
357     int opCmp(ref const(SourceLocation) other) @safe @nogc pure nothrow const {
358         if(path == other.path && line == other.line && column == other.column &&
359            offset == other.offset)
360             return 0;
361 
362         if(path < other.path) return -1;
363         if(path > other.path) return 1;
364         if(line < other.line) return -1;
365         if(line > other.line) return 1;
366         if(column < other.column) return -1;
367         if(column > other.column) return 1;
368         if(offset < other.offset) return -1;
369         if(offset > other.offset) return 1;
370         assert(false);
371     }
372 
373     string toString() @safe pure nothrow const {
374         import std.conv: text;
375         return text(`"`, path, `" `, line, ":", column, ":", offset);
376     }
377 }
378 
379 private struct ClientData {
380     /**
381        The D visitor delegate
382      */
383     CursorVisitor dvisitor;
384 }
385 
386 // This is the C function actually passed to libclang's clang_visitChildren
387 // The context (clientData) contains the D delegate that's then called on the
388 // (cursor, parent) pair
389 private extern(C) CXChildVisitResult cvisitor(CXCursor cursor, CXCursor parent, void* clientData_) {
390     auto clientData = cast(ClientData*) clientData_;
391     return cast(CXChildVisitResult) clientData.dvisitor(Cursor(cursor), Cursor(parent));
392 }
393 
394 
395 struct Type {
396 
397     mixin EnumD!("Kind", CXTypeKind, "CXType_");
398 
399     CXType cx;
400     Kind kind;
401     string spelling;
402 
403     this(CXType cx) @safe pure nothrow {
404         this.cx = cx;
405         this.kind = cast(Kind) cx.kind;
406         spelling = clang_getTypeSpelling(cx).toString;
407     }
408 
409     this(in Type other) @trusted pure nothrow {
410         import std.algorithm: map;
411         import std.array: array;
412 
413         this.cx.kind = other.cx.kind;
414         this.cx.data[] = other.cx.data[].map!(a => cast(void*) a).array;
415 
416         this(this.cx);
417     }
418 
419     this(in Kind kind) @safe @nogc pure nothrow {
420         this(kind, "");
421     }
422 
423     this(in Kind kind, in string spelling) @safe @nogc pure nothrow {
424         this.kind = kind;
425         this.spelling = spelling;
426     }
427 
428     Type pointee() @safe pure nothrow const {
429         return Type(clang_getPointeeType(cx));
430     }
431 
432     Type unelaborate() @safe nothrow const {
433         return Type(clang_Type_getNamedType(cx));
434     }
435 
436     Type canonical() @safe pure nothrow const {
437         return Type(clang_getCanonicalType(cx));
438     }
439 
440     Type returnType() @safe pure const {
441         return Type(clang_getResultType(cx));
442     }
443 
444     Type[] paramTypes() @safe pure const {
445         const numArgs = clang_getNumArgTypes(cx);
446         auto types = new Type[numArgs];
447 
448         foreach(i; 0 .. numArgs) {
449             types[i] = Type(clang_getArgType(cx, i));
450         }
451 
452         return types;
453     }
454 
455     bool isVariadicFunction() @safe @nogc pure nothrow const {
456         return cast(bool) clang_isFunctionTypeVariadic(cx);
457     }
458 
459     Type elementType() @safe pure nothrow const {
460         return Type(clang_getElementType(cx));
461     }
462 
463     long numElements() @safe @nogc pure nothrow const {
464         return clang_getNumElements(cx);
465     }
466 
467     long arraySize() @safe @nogc pure nothrow const {
468         return clang_getArraySize(cx);
469     }
470 
471     bool isConstQualified() @safe @nogc pure nothrow const {
472         return cast(bool)clang_isConstQualifiedType(cx);
473     }
474 
475     bool isVolatileQualified() @safe @nogc pure nothrow const {
476         return cast(bool)clang_isVolatileQualifiedType(cx);
477     }
478 
479     Cursor declaration() @safe nothrow const {
480         return Cursor(clang_getTypeDeclaration(cx));
481     }
482 
483     Type namedType() @safe pure nothrow const {
484         return Type(clang_Type_getNamedType(cx));
485     }
486 
487     bool opEquals(ref const(Type) other) @safe @nogc pure nothrow const {
488         return cast(bool) clang_equalTypes(cx, other.cx);
489     }
490 
491     bool opEquals(in Type other) @safe @nogc pure nothrow const {
492         return cast(bool) clang_equalTypes(cx, other.cx);
493     }
494 
495     bool isInvalid() @safe @nogc pure nothrow const {
496         return kind == Kind.Invalid;
497     }
498 
499     string toString() @safe pure nothrow const {
500         import std.conv: text;
501         try {
502             const pointeeText = pointee.isInvalid ? "" : text(", ", pointee);
503             return text("Type(", kind, `, "`, spelling, pointeeText, `")`);
504         } catch(Exception e)
505             assert(false, "Fatal error in Type.toString: " ~ e.msg);
506     }
507 }