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 }