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.get, 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 }