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