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