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