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 
8 module clang.TranslationUnit;
9 
10 import std.string;
11 
12 import clang.c.Index;
13 
14 import clang.Cursor;
15 import clang.Diagnostic;
16 import clang.File;
17 import clang.Index;
18 import clang.SourceLocation;
19 import clang.SourceRange;
20 import clang.Visitor;
21 
22 /** A single translation unit, which resides in an index.
23  */
24 struct TranslationUnit {
25     import std.typecons : Nullable;
26     import clang.Util;
27 
28     static private struct ContainTU {
29         mixin CX!("TranslationUnit");
30         // no need to call dispose because disposing of the Index also cleans
31         // up all TranslationUnits.
32     }
33 
34     ContainTU cx;
35     alias cx this;
36 
37     // the translation unit is destroyed when the dtor is ran.
38     @disable this(this);
39 
40     /**
41      * Trusted: on the assumption that clang_parseTranslationUnit is
42      * implemented by the LLVM team.
43      */
44     static TranslationUnit parse(ref Index index, string sourceFilename, string[] commandLineArgs,
45             CXUnsavedFile[] unsavedFiles = null,
46             uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord) @trusted {
47 
48         // dfmt off
49         // Trusted: on the assumption that the LLVM team are competent. That
50         // any problems that exist would have been found by now.
51         auto p = clang_parseTranslationUnit(
52                                        index.cx,
53                                        sourceFilename.toStringz,
54                                        strToCArray(commandLineArgs),
55                                        cast(int) commandLineArgs.length,
56                                        toCArray!(CXUnsavedFile)(unsavedFiles),
57                                        cast(uint) unsavedFiles.length,
58                                        options
59                                       );
60         // dfmt on
61 
62         auto tu = TranslationUnit(p);
63         index.cx.put(p);
64         return tu;
65     }
66 
67     /** Convenient function to create a TranslationUnit from source code via a
68      * parameter.
69      *
70      * Common use cases is unit testing.
71      *
72      * Trusted: on the assumption that
73      * clang.TranslationUnit.TranslationUnit.~this is correctly implemented.
74      */
75     static TranslationUnit parseString(ref Index index, string source, string[] commandLineArgs,
76             CXUnsavedFile[] unsavedFiles = null,
77             uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord) @safe {
78         import std.string : toStringz;
79 
80         string path = randomSourceFileName;
81         CXUnsavedFile file;
82         if (source.length == 0) {
83             file = CXUnsavedFile(path.toStringz, null, source.length);
84         } else {
85             file = CXUnsavedFile(path.toStringz, &source[0], source.length);
86         }
87 
88         auto in_memory_files = unsavedFiles ~ [file];
89 
90         return TranslationUnit.parse(index, path, commandLineArgs, in_memory_files, options);
91     }
92 
93     private static string randomSourceFileName() @safe {
94         import std.traits : fullyQualifiedName;
95         import std.path : buildPath;
96 
97         static string virtualPath() {
98             version (Windows)
99                 enum root = `C:\`;
100 
101             else
102                 enum root = "/";
103 
104             import std.conv : text;
105             import std.random;
106 
107             return buildPath(root, text(uniform(1, 10_000_000)));
108         }
109 
110         auto s = "random_source_filename.h";
111         return buildPath(virtualPath, s);
112     }
113 
114     package this(CXTranslationUnit cx) {
115         this.cx = ContainTU(cx);
116     }
117 
118     /// Returns: true if the TU compiled successfully.
119     bool isCompiled() {
120         import std.algorithm;
121 
122         alias predicate = x => x.severity != CXDiagnosticSeverity.error
123             && x.severity != CXDiagnosticSeverity.fatal;
124 
125         return diagnosticSet.all!predicate();
126     }
127 
128     bool isValid() @trusted {
129         return cx.isValid;
130     }
131 
132     /**
133      * Trusted: on the assumption that accessing the payload of the refcounted
134      * TranslationUnit is @safe.
135      */
136     @property DiagnosticVisitor diagnostics() @trusted {
137         return DiagnosticVisitor(cx);
138     }
139 
140     @property DiagnosticSet diagnosticSet() {
141         return DiagnosticSet(clang_getDiagnosticSetFromTU(cx));
142     }
143 
144     @property size_t numDiagnostics() {
145         return clang_getNumDiagnostics(cx);
146     }
147 
148     @property DeclarationVisitor declarations() {
149         auto c = Cursor(clang_getTranslationUnitCursor(cx));
150         return DeclarationVisitor(c);
151     }
152 
153     Nullable!File file(string filename) @trusted {
154         Nullable!File r;
155 
156         auto f = clang_getFile(cx, filename.toStringz);
157         if (f !is null) {
158             r = File(f);
159         }
160 
161         return r;
162     }
163 
164     File file() @safe {
165         return file(spelling).get;
166     }
167 
168     @property string spelling() @trusted {
169         return toD(clang_getTranslationUnitSpelling(cx));
170     }
171 
172     /**
173      * Trusted: on the assumption that the LLVM team is superb programmers.
174      */
175     @property Cursor cursor() @trusted {
176         auto r = clang_getTranslationUnitCursor(cx);
177         return Cursor(r);
178     }
179 
180     SourceLocation location(uint offset) @trusted {
181         CXFile file = clang_getFile(cx, spelling.toStringz);
182         return SourceLocation(clang_getLocationForOffset(cx, file, offset));
183     }
184 
185     SourceLocation location(string path, uint offset) @trusted {
186         CXFile file = clang_getFile(cx, path.toStringz);
187         return SourceLocation(clang_getLocationForOffset(cx, file, offset));
188     }
189 
190     SourceRange extent(uint startOffset, uint endOffset) {
191         CXFile file = clang_getFile(cx, spelling.toStringz);
192         auto start = clang_getLocationForOffset(cx, file, startOffset);
193         auto end = clang_getLocationForOffset(cx, file, endOffset);
194         return SourceRange(clang_getRange(start, end));
195     }
196 
197     package SourceLocation[] includeLocationsImpl(Range)(Range cursors) @trusted {
198         // `cursors` range should at least contain all global
199         // preprocessor cursors, although it can contain more.
200 
201         bool[string] stacked;
202         bool[string] included;
203         SourceLocation[] locationStack;
204         SourceLocation[] locations = [location("", 0), location(file.name, 0)];
205 
206         foreach (idx, cursor; cursors) {
207             if (cursor.kind == CXCursorKind.inclusionDirective) {
208                 auto path = cursor.location.spelling.file.name;
209                 auto ptr = path in stacked;
210 
211                 if (ptr !is null && *ptr) {
212                     while (locationStack[$ - 1].path != path) {
213                         stacked[locationStack[$ - 1].path] = false;
214                         locations ~= locationStack[$ - 1];
215                         locationStack = locationStack[0 .. $ - 1];
216                     }
217 
218                     stacked[path] = false;
219                     locations ~= locationStack[$ - 1];
220                     locationStack = locationStack[0 .. $ - 1];
221                 }
222 
223                 if ((cursor.include.file.name in included) is null) {
224                     locationStack ~= cursor.extent.end;
225                     stacked[path] = true;
226                     locations ~= location(cursor.include.file.name, 0);
227                     included[cursor.include.file.name] = true;
228                 }
229             }
230         }
231 
232         while (locationStack.length != 0) {
233             locations ~= locationStack[$ - 1];
234             locationStack = locationStack[0 .. $ - 1];
235         }
236 
237         return locations;
238     }
239 
240     SourceLocation[] includeLocations() {
241         return includeLocationsImpl(cursor.all);
242     }
243 
244     package ulong delegate(SourceLocation) relativeLocationAccessorImpl(Range)(Range cursors) {
245         // `cursors` range should at least contain all global
246         // preprocessor cursors, although it can contain more.
247 
248         SourceLocation[] locations = includeLocationsImpl(cursors);
249 
250         struct Entry {
251             uint index;
252             SourceLocation location;
253 
254             int opCmp(ref const Entry s) const {
255                 return location.offset < s.location.offset ? -1 : 1;
256             }
257 
258             int opCmp(ref const SourceLocation s) const {
259                 return location.offset < s.offset + 1 ? -1 : 1;
260             }
261         }
262 
263         Entry[][string] map;
264 
265         foreach (size_t index, location; locations)
266             map[location.path] ~= Entry(cast(uint) index, location);
267 
268         uint findIndex(SourceLocation a) {
269             auto entries = map[a.path];
270 
271             import std.range;
272 
273             auto lower = assumeSorted(entries).lowerBound(a);
274 
275             return lower.empty ? 0 : lower.back.index;
276         }
277 
278         ulong accessor(SourceLocation location) {
279             return ((cast(ulong) findIndex(location)) << 32) | (cast(ulong) location.offset);
280         }
281 
282         return &accessor;
283     }
284 
285     size_t delegate(SourceLocation) relativeLocationAccessor() {
286         return relativeLocationAccessorImpl(cursor.all);
287     }
288 }
289 
290 string dumpAST(ref TranslationUnit tu, bool skipIncluded = false) {
291     import std.array : appender;
292     import clang.Cursor : dumpAST;
293 
294     auto result = appender!string();
295     auto c = tu.cursor;
296 
297     if (skipIncluded) {
298         File file = tu.file;
299         dumpAST(c, result, 0, &file);
300     } else {
301         dumpAST(c, result, 0);
302     }
303 
304     return result.data;
305 }
306 
307 struct DiagnosticVisitor {
308     private CXTranslationUnit translatoinUnit;
309 
310     this(CXTranslationUnit translatoinUnit) @safe {
311         this.translatoinUnit = translatoinUnit;
312     }
313 
314     size_t length() {
315         return clang_getNumDiagnostics(translatoinUnit);
316     }
317 
318     int opApply(int delegate(ref Diagnostic) dg) {
319         int result;
320 
321         foreach (i; 0 .. length) {
322             auto diag = clang_getDiagnostic(translatoinUnit, cast(uint) i);
323             auto dDiag = Diagnostic(diag);
324             result = dg(dDiag);
325 
326             if (result)
327                 break;
328         }
329 
330         return result;
331     }
332 }