1 /**
2  * Copyright: Copyright (c) 2011 Jacob Carlborg. All rights reserved.
3  * Authors: Jacob Carlborg
4  * Version: Initial created: Oct 1, 2011
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
6  */
7 module clang.TranslationUnit;
8 
9 import std..string;
10 
11 import clang.c.Index;
12 import clang.Cursor;
13 import clang.Diagnostic;
14 import clang.File;
15 import clang.Index;
16 import clang.SourceLocation;
17 import clang.SourceRange;
18 import clang.Token;
19 import clang.Util;
20 import clang.Visitor;
21 
22 struct TranslationUnit
23 {
24     mixin CX;
25 
26     string sourceString = null;
27 
28     static TranslationUnit parse (
29         Index index,
30         string sourceFilename,
31         const string[] commandLineArgs = ["-Wno-missing-declarations"],
32         CXUnsavedFile[] unsavedFiles = null,
33         uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord)
34     {
35         string[] arguments = commandLineArgs.dup;
36 
37         auto version_ = clangVersion();
38 
39         if (version_.major == 3 && version_.minor == 7)
40             arguments ~= "-D__int64=long long";
41 
42         return TranslationUnit(
43             clang_parseTranslationUnit(
44                 index.cx,
45                 sourceFilename.toStringz,
46                 strToCArray(arguments),
47                 cast(int) arguments.length,
48                 toCArray!(CXUnsavedFile)(unsavedFiles),
49                 cast(uint) unsavedFiles.length,
50                 options));
51     }
52 
53     static TranslationUnit parseString (
54         Index index,
55         string source,
56         string[] commandLineArgs = ["-Wno-missing-declarations"],
57         CXUnsavedFile[] unsavedFiles = null,
58         uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord)
59     {
60         import std.file;
61 
62         auto file = namedTempFile("dstep", ".h");
63         auto name = file.name();
64         file.write(source);
65         file.flush();
66         file.detach();
67 
68         auto translationUnit = TranslationUnit.parse(
69             index,
70             name,
71             commandLineArgs,
72             unsavedFiles,
73             options);
74 
75         remove(name);
76 
77         translationUnit.sourceString = source;
78 
79         return translationUnit;
80     }
81 
82     package this (CXTranslationUnit cx)
83     {
84         this.cx = cx;
85     }
86 
87     @property DiagnosticVisitor diagnostics ()
88     {
89         return DiagnosticVisitor(cx);
90     }
91 
92     @property DiagnosticSet diagnosticSet ()
93     {
94         return DiagnosticSet(clang_getDiagnosticSetFromTU(cx));
95     }
96 
97     @property size_t numDiagnostics ()
98     {
99         return clang_getNumDiagnostics(cx);
100     }
101 
102     bool isCompiled()
103     {
104         import std.algorithm;
105 
106         alias predicate =
107             x => x.severity != CXDiagnosticSeverity.error &&
108                 x.severity != CXDiagnosticSeverity.fatal;
109 
110         return diagnosticSet.all!predicate();
111     }
112 
113     @property DeclarationVisitor declarations ()
114     {
115         return DeclarationVisitor(clang_getTranslationUnitCursor(cx));
116     }
117 
118     File file (string filename)
119     {
120         return File(clang_getFile(cx, filename.toStringz));
121     }
122 
123     File file ()
124     {
125         return file(spelling);
126     }
127 
128     @property string spelling ()
129     {
130         return toD(clang_getTranslationUnitSpelling(cx));
131     }
132 
133     @property string source ()
134     {
135         import std.file : readText;
136         return sourceString ? sourceString : readText(spelling);
137     }
138 
139     @property Cursor cursor ()
140     {
141         auto r = clang_getTranslationUnitCursor(cx);
142         return Cursor(r);
143     }
144 
145     SourceLocation location (uint offset)
146     {
147         CXFile file = clang_getFile(cx, spelling.toStringz);
148         return SourceLocation(clang_getLocationForOffset(cx, file, offset));
149     }
150 
151     SourceLocation location (string path, uint offset)
152     {
153         CXFile file = clang_getFile(cx, path.toStringz);
154         return SourceLocation(clang_getLocationForOffset(cx, file, offset));
155     }
156 
157     SourceRange extent (uint startOffset, uint endOffset)
158     {
159         CXFile file = clang_getFile(cx, spelling.toStringz);
160         auto start = clang_getLocationForOffset(cx, file, startOffset);
161         auto end = clang_getLocationForOffset(cx, file, endOffset);
162         return SourceRange(clang_getRange(start, end));
163     }
164 
165     package SourceLocation[] includeLocationsImpl(Range)(Range cursors)
166     {
167         // `cursors` range should at least contain all global
168         // preprocessor cursors, although it can contain more.
169 
170         Set!string stacked;
171         Set!string included;
172         SourceLocation[] locationStack;
173         SourceLocation[] locations = [ location("", 0), location(file.name, 0) ];
174 
175         foreach (cursor; cursors)
176         {
177             if (cursor.kind == CXCursorKind.inclusionDirective)
178             {
179                 auto ptr = cursor.path in stacked;
180 
181                 if (stacked.contains(cursor.path))
182                 {
183                     while (locationStack[$ - 1].path != cursor.path)
184                     {
185                         stacked.remove(locationStack[$ - 1].path);
186                         locations ~= locationStack[$ - 1];
187                         locationStack = locationStack[0 .. $ - 1];
188                     }
189 
190                     stacked.remove(cursor.path);
191                     locations ~= locationStack[$ - 1];
192                     locationStack = locationStack[0 .. $ - 1];
193                 }
194 
195                 if ((cursor.includedPath in included) is null)
196                 {
197                     locationStack ~= cursor.extent.end;
198                     stacked.add(cursor.path);
199                     locations ~= location(cursor.includedPath, 0);
200                     included.add(cursor.includedPath);
201                 }
202             }
203         }
204 
205         while (locationStack.length != 0)
206         {
207             locations ~= locationStack[$ - 1];
208             locationStack = locationStack[0 .. $ - 1];
209         }
210 
211         return locations;
212     }
213 
214     SourceLocation[] includeLocations()
215     {
216         return includeLocationsImpl(cursor.all);
217     }
218 
219     package ulong delegate (SourceLocation)
220         relativeLocationAccessorImpl(Range)(Range cursors)
221     {
222         // `cursors` range should at least contain all global
223         // preprocessor cursors, although it can contain more.
224 
225         SourceLocation[] locations = includeLocationsImpl(cursors);
226 
227         struct Entry
228         {
229             uint index;
230             SourceLocation location;
231 
232             int opCmp(ref const Entry s) const
233             {
234                 return location.offset < s.location.offset ? -1 : 1;
235             }
236 
237             int opCmp(ref const SourceLocation s) const
238             {
239                 return location.offset < s.offset + 1 ? -1 : 1;
240             }
241         }
242 
243         Entry[][string] map;
244 
245         foreach (uint index, location; locations)
246             map[location.path] ~= Entry(index, location);
247 
248         uint findIndex(SourceLocation a)
249         {
250             auto entries = map[a.path];
251 
252             import std.range;
253 
254             auto lower = assumeSorted(entries).lowerBound(a);
255 
256             return lower.empty ? 0 : lower.back.index;
257         }
258 
259         ulong accessor(SourceLocation location)
260         {
261             return ((cast(ulong) findIndex(location)) << 32) |
262                 (cast(ulong) location.offset);
263         }
264 
265         return &accessor;
266     }
267 
268     ulong delegate (SourceLocation)
269         relativeLocationAccessor()
270     {
271         return relativeLocationAccessorImpl(cursor.all);
272     }
273 
274     bool delegate (SourceLocation, SourceLocation)
275         relativeLocationLessOp()
276     {
277         auto accessor = relativeLocationAccessor();
278 
279         bool lessOp(SourceLocation a, SourceLocation b)
280         {
281             if (a.file == b.file)
282                 return a.offset < b.offset;
283             else
284                 return accessor(a) < accessor(b);
285         }
286 
287         return &lessOp;
288     }
289 
290     bool delegate (Cursor, Cursor)
291         relativeCursorLocationLessOp()
292     {
293         auto accessor = relativeLocationAccessor();
294 
295         bool lessOp(Cursor a, Cursor b)
296         {
297             if (a.file == b.file)
298                 return a.location.offset < b.location.offset;
299             else
300                 return accessor(a.location) < accessor(b.location);
301         }
302 
303         return &lessOp;
304     }
305 
306     private struct TokenRange
307     {
308         CXTranslationUnit cx;
309         CXToken* tokens;
310         uint numTokens;
311         uint currentToken;
312 
313         Token makeToken(CXToken token)
314         {
315             return Token(
316                 clang_getTokenKind(token).toD,
317                 clang_getTokenSpelling(cx, token).toD,
318                 SourceRange(clang_getTokenExtent(cx, token)));
319         }
320 
321         Token front()
322         {
323             return makeToken(tokens[currentToken]);
324         }
325 
326         bool empty()
327         {
328             return numTokens == 0 || numTokens == currentToken;
329         }
330 
331         void popFront()
332         {
333             currentToken++;
334         }
335 
336         void dispose()
337         {
338             clang_disposeTokens(cx, tokens, numTokens);
339         }
340     }
341 
342     private static TokenRange tokenizeImpl(CXTranslationUnit cx, SourceRange extent)
343     {
344         auto range = TokenRange(cx);
345         clang_tokenize(cx, extent.cx, &range.tokens, &range.numTokens);
346         return range;
347     }
348 
349     package static Token[] tokenize(CXTranslationUnit cx, SourceRange extent)
350     {
351         import std.array : array;
352         import std.algorithm : stripRight;
353 
354         auto range = tokenizeImpl(cx, extent);
355         auto tokens = range.array;
356         range.dispose();
357 
358         // For some reason libclang returns some tokens out of cursors extent.cursor
359         return tokens.stripRight!(token => !intersects(extent, token.extent));
360     }
361 
362     package static Token[] tokenizeNoComments(CXTranslationUnit cx, SourceRange extent)
363     {
364         import std.array : array;
365         import std.algorithm : filter, stripRight;
366 
367         auto range = tokenizeImpl(cx, extent);
368         auto tokens = range.filter!(e => e.kind != TokenKind.comment).array;
369         range.dispose();
370 
371         // For some reason libclang returns some tokens out of cursors extent.cursor
372         return tokens.stripRight!(token => !intersects(extent, token.extent));
373     }
374 
375     Token[] tokenize(SourceRange extent)
376     {
377         return tokenize(cx, extent);
378     }
379 
380     Token[] tokenizeNoComments(SourceRange extent)
381     {
382         return tokenizeNoComments(cx, extent);
383     }
384 
385     Token[] tokens()
386     {
387         return tokenize(extent(0, cast(uint) source.length));
388     }
389 
390     Token[] tokensNoComments()
391     {
392         return tokenizeNoComments(extent(0, cast(uint) source.length));
393     }
394 
395     bool isFileMultipleIncludeGuarded(string path)
396     {
397         auto file = clang_getFile(cx, path.toStringz);
398         return clang_isFileMultipleIncludeGuarded(cx, file) != 0;
399     }
400 
401     bool isMultipleIncludeGuarded()
402     {
403         return isFileMultipleIncludeGuarded(spelling);
404     }
405 
406     string dumpAST(bool skipIncluded = true)
407     {
408         import std.array : appender;
409 
410         auto result = appender!string();
411 
412         if (skipIncluded)
413         {
414             File file = this.file;
415             cursor.dumpAST(result, 0, &file);
416         }
417         else
418         {
419             cursor.dumpAST(result, 0);
420         }
421 
422         return result.data;
423     }
424 }
425 
426 struct DiagnosticVisitor
427 {
428     private CXTranslationUnit translatoinUnit;
429 
430     this (CXTranslationUnit translatoinUnit)
431     {
432         this.translatoinUnit = translatoinUnit;
433     }
434 
435     size_t length ()
436     {
437         return clang_getNumDiagnostics(translatoinUnit);
438     }
439 
440     int opApply (int delegate (ref Diagnostic) dg)
441     {
442         int result;
443 
444         foreach (i ; 0 .. length)
445         {
446             auto diag = clang_getDiagnostic(translatoinUnit, cast(uint) i);
447             auto dDiag = Diagnostic(diag);
448             result = dg(dDiag);
449 
450             if (result)
451                 break;
452         }
453 
454         return result;
455     }
456 }
457 
458 Token[] tokenize(string source)
459 {
460     Index index = Index(false, false);
461     auto translUnit = TranslationUnit.parseString(index, source);
462     return translUnit.tokenize(translUnit.extent(0, cast(uint) source.length));
463 }
464 
465 Token[] tokenizeNoComments(string source)
466 {
467     Index index = Index(false, false);
468     auto translUnit = TranslationUnit.parseString(index, source);
469     return translUnit.tokenizeNoComments(
470         translUnit.extent(0, cast(uint) source.length));
471 }